diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c3a8dc2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.github +.idea +.vscode +**/.DS_Store +**/*.iml +**/target +data +node_modules +frontend/node_modules +frontend/dist +backend/web/src/main/resources/static diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7dca259 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node-terminal", + "name": "Docker: Chen Backend", + "request": "launch", + "command": "docker compose up --build", + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aa0bb87 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "java.configuration.runtimes": [ + { + "name": "JavaSE-21", + "path": "/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home", + "default": true + }, + { + "name": "JavaSE-17", + "path": "/Users/guang/Library/Java/JavaVirtualMachines/jbr-17.0.9/Contents/Home" + } + ], + "java.jdt.ls.java.home": "/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home", + "java.compile.nullAnalysis.mode": "disabled" +} diff --git a/Dockerfile b/Dockerfile index 2518fb9..53c7944 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,9 @@ -FROM jumpserver/chen-base:20260701_092702 AS stage-build +FROM jumpserver/chen-base:20260731_025640 AS stage-build ENV LANG=en_US.UTF-8 WORKDIR /opt/chen/ COPY . . -RUN cd frontend \ - && npm run build RUN mvn clean package -Dmaven.test.skip=true diff --git a/Dockerfile.wisp-dev b/Dockerfile.wisp-dev new file mode 100644 index 0000000..ff330cf --- /dev/null +++ b/Dockerfile.wisp-dev @@ -0,0 +1,52 @@ +# syntax=docker/dockerfile:1.7 + +ARG GO_VERSION=1.25 +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS stage-build + +ARG TARGETOS=linux +ARG TARGETARCH=amd64 +ARG WISP_BUILD_PROCS=2 +ARG WISP_BUILD_MEMORY=1GiB + +ENV CGO_ENABLED=0 +WORKDIR /opt/wisp + +COPY --from=wisp-source go.mod go.sum ./ +RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \ + GOMAXPROCS=${WISP_BUILD_PROCS} \ + GOMEMLIMIT=${WISP_BUILD_MEMORY} \ + go mod download + +COPY --from=wisp-source main.go ./ +COPY --from=wisp-source cmd ./cmd +COPY --from=wisp-source pkg ./pkg +COPY --from=wisp-source protobuf-go ./protobuf-go + +RUN --mount=type=cache,target=/go/pkg/mod,sharing=locked \ + --mount=type=cache,target=/root/.cache/go-build,sharing=locked \ + GOMAXPROCS=${WISP_BUILD_PROCS} \ + GOMEMLIMIT=${WISP_BUILD_MEMORY} \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ + go build -p=${WISP_BUILD_PROCS} -trimpath -o /out/wisp . + +FROM debian:bookworm-slim + +ARG APT_MIRROR=http://deb.debian.org +RUN set -ex \ + && sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list.d/debian.sources \ + && apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates netcat-openbsd \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/wisp +COPY --from=stage-build /out/wisp ./wisp + +VOLUME ["/opt/wisp/data"] +EXPOSE 9090 + +HEALTHCHECK --interval=2s --timeout=1s --start-period=5s --retries=30 \ + CMD ["nc", "-z", "127.0.0.1", "9090"] + +CMD ["./wisp"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..832ab95 --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +SHELL := /bin/bash +.DEFAULT_GOAL := help +.NOTPARALLEL: + +DOCKER_COMPOSE ?= docker compose +COMPOSE_PARALLEL_LIMIT ?= 1 +WISP_IMAGE_REPOSITORY ?= ghcr.io/jumpserver/wisp +WISP_BRANCH ?= +export WISP_BRANCH +WISP_TAG ?= $(shell ./scripts/resolve-wisp-tag.sh) +WISP_IMAGE ?= $(WISP_IMAGE_REPOSITORY):$(WISP_TAG) +WISP_PULL_POLICY ?= missing + +export COMPOSE_PARALLEL_LIMIT +export WISP_IMAGE_REPOSITORY +export WISP_TAG +export WISP_IMAGE +export WISP_PULL_POLICY +export WISP_SOURCE + +DEV_COMPOSE_FILES := -f docker-compose.yml -f docker-compose.wisp-dev.yml +ifneq ($(strip $(WISP_SOURCE)),) +DEV_COMPOSE_FILES += -f docker-compose.wisp-source.yml +endif +DEV_COMPOSE := $(DOCKER_COMPOSE) $(DEV_COMPOSE_FILES) + +.PHONY: help dev run dev-run dev-up dev-wisp dev-wisp-image dev-logs dev-ps dev-config dev-down proto-sync + +help: + @printf '%s\n' \ + 'Chen development commands:' \ + ' make dev Run in the foreground (make dev run also works)' \ + ' make dev-up Build and run in the background' \ + ' make dev-wisp Rebuild/recreate only Wisp' \ + ' make dev-wisp-image Show the resolved Wisp image' \ + ' make dev-logs Follow Chen and Wisp logs' \ + ' make dev-ps Show development services' \ + ' make dev-config Render the merged Compose configuration' \ + ' make dev-down Stop and remove development services' \ + ' make proto-sync Copy generated Java protobuf files from Wisp' \ + '' \ + 'Wisp source mode:' \ + ' make dev WISP_SOURCE=/absolute/path/to/wisp' \ + '' \ + 'Wisp image mode:' \ + ' Wisp tag defaults to the current Chen tracking branch' \ + ' make dev WISP_TAG=latest' \ + ' make dev WISP_IMAGE=registry/wisp:tag WISP_PULL_POLICY=always' + +# Both spellings are supported, including one invocation written as +# `make dev run`. GNU Make updates their shared prerequisite only once. +dev run: dev-run + @: + +dev-run: + @$(DEV_COMPOSE) up --build + +dev-up: + @$(DEV_COMPOSE) up -d --build + +dev-wisp: + @$(DEV_COMPOSE) up -d --build --no-deps wisp + +dev-wisp-image: + @printf '%s\n' '$(WISP_IMAGE)' + +dev-logs: + @$(DEV_COMPOSE) logs -f chen-backend wisp + +dev-ps: + @$(DEV_COMPOSE) ps + +dev-config: + @$(DEV_COMPOSE) config + +dev-down: + @$(DEV_COMPOSE) down + +proto-sync: + @./scripts/sync-wisp-proto.sh diff --git a/README.md b/README.md index eda7016..3d2cee8 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,81 @@ Chen is implemented in Java, and its name is derived from the Dota hero [Chen](h - [x] SQL Server (X-Pack) - [x] Oracle (X-Pack) - [x] DB2 (X-Pack) + +## Develop with Wisp + +The default Chen image contains a released Wisp binary. The development +override separates Wisp into its own service, starts Chen's Java process +directly, and connects Chen to `wisp:9090` over the Compose network. + +### Use a Wisp image + +`make dev` defaults to `ghcr.io/jumpserver/wisp:`. The branch is +resolved from the current branch's tracking branch first, then from the local +branch. JumpServer-style branches such as `pr@new_terminal@feat_ai` resolve to +their base branch (`new_terminal`), and characters that cannot appear in a +Docker tag are replaced with `-`. A detached checkout falls back to `latest`. + +Check the result without starting containers: + +```bash +make dev-wisp-image +``` + +The matching tag must have been published in the Wisp container registry. If +it is not available, select a known tag with `WISP_TAG`, use `WISP_SOURCE` as +described below, or set `WISP_IMAGE` to a private-registry or locally built +image: + +```bash +make dev WISP_TAG=latest + +make dev \ + WISP_IMAGE=registry.example.com/wisp:my-branch \ + WISP_PULL_POLICY=always +``` + +Use `WISP_BRANCH` to resolve a tag from a branch other than the checked-out +Chen branch. `WISP_IMAGE_REPOSITORY` changes only the registry/repository while +retaining automatic tag selection. For an image already built locally, set +`WISP_PULL_POLICY=never`. + +Calling `docker compose` directly (without the Makefile) retains the Compose +file's `ghcr.io/jumpserver/wisp:latest` fallback. + +### Build from Wisp source + +Add `docker-compose.wisp-source.yml` and set `WISP_SOURCE` to any local checkout +path. It does not need to be `../wisp`: + +```bash +make dev WISP_SOURCE=/absolute/path/to/wisp +``` + +`WISP_SOURCE` is a BuildKit named context, so it may also be a Git context URL +when the changes have been pushed to a branch. Uncommitted changes require a +local directory. + +After a Go-only Wisp change, rebuild just Wisp; the Java container can stay up +and its gRPC channel will reconnect: + +```bash +make dev-wisp WISP_SOURCE=/absolute/path/to/wisp +``` + +If a Wisp `.proto` file changes, regenerate both Go and Java outputs in Wisp, +then synchronize the Java output before rebuilding Chen: + +```bash +make -C /absolute/path/to/wisp proto-go proto-java +make proto-sync WISP_SOURCE=/absolute/path/to/wisp +make dev-up WISP_SOURCE=/absolute/path/to/wisp +``` + +Use `make dev-up` for detached startup, `make dev-logs` to follow both service +logs, and `make dev-down` to stop the environment. Run `make help` for all +development commands. `make dev run` is accepted as an alias of `make dev`. + +The build and runtime limits can be adjusted with `WISP_BUILD_PROCS`, +`WISP_BUILD_MEMORY`, `WISP_CPUS`, `WISP_MEMORY_LIMIT`, `CHEN_CPUS`, and +`CHEN_MEMORY_LIMIT`. diff --git a/backend/framework/pom.xml b/backend/framework/pom.xml index aaedccc..4bbfa36 100644 --- a/backend/framework/pom.xml +++ b/backend/framework/pom.xml @@ -12,6 +12,11 @@ framework + + org.junit.jupiter + junit-jupiter + test + com.github.freva ascii-table @@ -19,9 +24,9 @@ - com.alibaba + com.jumpserver druid - 1.2.18 + ${druid.version} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java index 1791a86..64b5436 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/AbstractConsole.java @@ -5,6 +5,7 @@ import org.jumpserver.chen.framework.datasource.Datasource; import org.jumpserver.chen.framework.console.component.Logger; import org.jumpserver.chen.framework.console.component.Messager; +import org.jumpserver.chen.framework.console.context.ConsoleContext; import org.jumpserver.chen.framework.ws.io.PacketIO; import org.jumpserver.chen.framework.console.entity.request.Connect; import org.springframework.web.socket.WebSocketSession; @@ -20,18 +21,25 @@ public abstract class AbstractConsole implements Console { private final PacketIO packetIO; private final Messager messager; private String title; - private String nodeKey; + private final ConsoleContext context; public String getTitle() { return this.title; } public void onInit(Connect connect) { - this.packetIO.sendPacket("init", Map.of("title", this.title)); + this.packetIO.sendPacket("init", Map.of( + "title", this.title, + "consoleId", this.packetIO.getWsSession().getId() + )); } - protected AbstractConsole(Datasource datasource, WebSocketSession ws, String nodeKey) { - this.nodeKey = nodeKey; + public String getNodeKey() { + return this.context.nodeKey(); + } + + protected AbstractConsole(Datasource datasource, WebSocketSession ws, ConsoleContext context) { + this.context = context; this.datasource = datasource; this.packetIO = new PacketIO(ws); this.consoleLogger = new Logger(this.packetIO); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java index 9ea44e6..c7e6a19 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/Console.java @@ -3,10 +3,12 @@ import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.console.context.ConsoleContext; public interface Console { String getTitle(); String getNodeKey(); + ConsoleContext getContext(); void onInit(Connect connect); void handle(Packet packet); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/ConsoleStatementBoundaryScanner.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/ConsoleStatementBoundaryScanner.java new file mode 100644 index 0000000..53ef5a9 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/ConsoleStatementBoundaryScanner.java @@ -0,0 +1,182 @@ +package org.jumpserver.chen.framework.console; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; + +import java.sql.SQLException; + +/** + * Validates that raw console input is one database command without rewriting it. + */ +final class ConsoleStatementBoundaryScanner { + private static final String MULTIPLE_STATEMENTS_ERROR = + "Console raw execution accepts only one top-level database command"; + + private ConsoleStatementBoundaryScanner() { + } + + static void requireSingleStatement(String sql, DbType dbType) throws SQLException { + if (sql == null || sql.isBlank()) { + throw new SQLException("Console raw execution requires a database command"); + } + + try { + var statements = SQLUtils.parseStatements(sql, dbType); + if (statements.size() == 1) { + return; + } + if (statements.size() > 1) { + throw new SQLException(MULTIPLE_STATEMENTS_ERROR); + } + } catch (RuntimeException ignored) { + // Raw mode exists for commands the dialect parser does not understand. The conservative + // fallback below recognizes only top-level delimiters and keeps quoted/dollar-quoted + // procedure bodies opaque; it never changes the command sent to the JDBC driver. + } + + requireSingleOpaqueStatement(sql); + } + + private static void requireSingleOpaqueStatement(String sql) throws SQLException { + int completedStatements = 0; + boolean hasStatementContent = false; + int blockCommentDepth = 0; + char quote = 0; + String dollarQuote = null; + + for (int index = 0; index < sql.length(); index++) { + char current = sql.charAt(index); + char next = index + 1 < sql.length() ? sql.charAt(index + 1) : 0; + + if (dollarQuote != null) { + if (sql.startsWith(dollarQuote, index)) { + index += dollarQuote.length() - 1; + dollarQuote = null; + } + continue; + } + + if (quote != 0) { + if (current == '\\' && index + 1 < sql.length()) { + index++; + continue; + } + if (current == quote) { + if (next == quote) { + index++; + } else { + quote = 0; + } + } + continue; + } + + if (blockCommentDepth > 0) { + if (current == '/' && next == '*') { + blockCommentDepth++; + index++; + } else if (current == '*' && next == '/') { + blockCommentDepth--; + index++; + } + continue; + } + + if (current == '-' && next == '-') { + index = skipLineComment(sql, index + 2); + continue; + } + if (current == '/' && next == '*') { + blockCommentDepth = 1; + index++; + continue; + } + if (current == '\'' || current == '"' || current == '`') { + quote = current; + hasStatementContent = true; + continue; + } + if (current == '$') { + String delimiter = dollarQuoteDelimiter(sql, index); + if (delimiter != null) { + dollarQuote = delimiter; + hasStatementContent = true; + index += delimiter.length() - 1; + continue; + } + } + if (current == ';') { + if (hasStatementContent) { + completedStatements++; + hasStatementContent = false; + if (completedStatements > 1) { + throw new SQLException(MULTIPLE_STATEMENTS_ERROR); + } + } + continue; + } + if (!Character.isWhitespace(current)) { + if (completedStatements > 0) { + throw new SQLException(MULTIPLE_STATEMENTS_ERROR); + } + hasStatementContent = true; + } + } + + if (hasStatementContent) { + completedStatements++; + } + if (completedStatements == 0) { + throw new SQLException("Console raw execution requires a database command"); + } + if (completedStatements > 1) { + throw new SQLException(MULTIPLE_STATEMENTS_ERROR); + } + } + + private static int skipLineComment(String sql, int index) { + while (index < sql.length()) { + char current = sql.charAt(index); + if (current == '\n' || current == '\r') { + return index; + } + index++; + } + return sql.length() - 1; + } + + private static String dollarQuoteDelimiter(String sql, int start) { + if (start > 0 && isIdentifierPart(sql.charAt(start - 1))) { + return null; + } + int end = sql.indexOf('$', start + 1); + if (end < 0) { + return null; + } + String tag = sql.substring(start + 1, end); + if (!tag.isEmpty()) { + if (!isTagStart(tag.charAt(0))) { + return null; + } + for (int index = 1; index < tag.length(); index++) { + if (!isTagPart(tag.charAt(index))) { + return null; + } + } + } + String delimiter = sql.substring(start, end + 1); + return sql.indexOf(delimiter, end + 1) >= 0 ? delimiter : null; + } + + private static boolean isTagStart(char value) { + return value == '_' || Character.isLetter(value); + } + + private static boolean isTagPart(char value) { + return isTagStart(value) || Character.isDigit(value); + } + + private static boolean isIdentifierPart(char value) { + return isTagPart(value) || value == '$'; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java index e808d64..8055709 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/DataViewConsole.java @@ -1,21 +1,29 @@ package org.jumpserver.chen.framework.console; import com.google.gson.Gson; -import org.apache.commons.lang3.StringUtils; +import lombok.Getter; import org.jumpserver.chen.framework.console.action.DataViewAction; +import org.jumpserver.chen.framework.console.context.ConsoleContext; import org.jumpserver.chen.framework.console.dataview.DataView; import org.jumpserver.chen.framework.console.dataview.UpdateDataView; import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; import org.jumpserver.chen.framework.console.entity.response.Message; import org.jumpserver.chen.framework.console.state.State; import org.jumpserver.chen.framework.console.state.StateManager; import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.edit.TableBrowseSaveExecutionContext; +import org.jumpserver.chen.framework.datasource.edit.TableChangesPreviewService; +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; +import org.jumpserver.chen.framework.datasource.edit.TableEditContext; +import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Button; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; -import org.jumpserver.chen.framework.utils.TreeUtils; +import org.jumpserver.chen.framework.utils.PageUtils; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.wisp.Common; import org.springframework.web.socket.WebSocketSession; @@ -23,29 +31,49 @@ import java.sql.SQLException; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; public class DataViewConsole extends AbstractConsole { + private static final String PACKET_SAVE_CHANGES_PREVIEW_RESULT = "save_changes_preview_result"; + private static final String PACKET_SAVE_CHANGES_RESULT = "save_changes_result"; + private static final long WARNING_DIALOG_TIMEOUT_SECONDS = 300; + private final TableChangesPreviewService tableChangesPreviewService = new TableChangesPreviewService(); + private final TableChangesSaveService tableChangesSaveService = new TableChangesSaveService(); private DataView tableDataView; private StateManager stateManager; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ReentrantLock executionLock = new ReentrantLock(); - private String schema; - private String table; + @Getter + private final String schema; + @Getter + private final String table; private static final Gson GSON = new Gson(); - public DataViewConsole(Datasource datasource, WebSocketSession ws, String nodeKey) { - super(datasource, ws, nodeKey); + public DataViewConsole(Datasource datasource, WebSocketSession ws, ConsoleContext context) { + super(datasource, ws, context); + this.schema = context.schema(); + this.table = context.table(); } @Override public void onInit(Connect connect) { - this.schema = TreeUtils.getValue(connect.getNodeKey(), "schema"); - this.table = StringUtils.isEmpty(TreeUtils.getValue(connect.getNodeKey(), "table")) ? - TreeUtils.getValue(connect.getNodeKey(), "view") : TreeUtils.getValue(connect.getNodeKey(), "table"); + if (!this.beginExecution()) { + return; + } + try { + this.initialize(connect); + } finally { + this.executionLock.unlock(); + } + } + private void initialize(Connect connect) { var title = ""; try { title = this.generateConsoleName(); @@ -72,13 +100,32 @@ private String generateConsoleName() { @Override public void handle(Packet packet) { - switch (packet.getType()) { - case "ping" -> this.getPacketIO().sendPacket("pong", null); - case Packet.TYPE_DATA_VIEW_ACTION -> { - var action = GSON.fromJson(GSON.toJson(packet.getData()), DataViewAction.class); - this.onDataViewAction(action); + if (!this.beginExecution()) { + return; + } + try { + switch (packet.getType()) { + case "ping" -> this.getPacketIO().sendPacket("pong", null); + case Packet.TYPE_DATA_VIEW_ACTION -> { + var action = GSON.fromJson(GSON.toJson(packet.getData()), DataViewAction.class); + this.onDataViewAction(action); + } } + } finally { + this.executionLock.unlock(); + } + } + + private boolean beginExecution() { + if (this.closed.get()) { + return false; + } + this.executionLock.lock(); + if (this.closed.get()) { + this.executionLock.unlock(); + return false; } + return true; } public void onConnect(Connect connect) { @@ -112,11 +159,22 @@ public void createDataView(String schemaName, String tableName) { var session = SessionManager.getCurrentSession(); dataView.setLoadDataInterface((sqlQueryParams) -> { - var plan = this.getDatasource() + var basePlan = this.getDatasource() .getConnectionManager() .getSqlActuator() .createPlan(schemaName, tableName, null); - var sql = plan.getTargetSQL(); + final String sql; + try { + sql = PageUtils.filter( + basePlan.getTargetSQL(), + this.getDatasource().getDruidDbType(), + sqlQueryParams.getFilter() + ); + } catch (RuntimeException e) { + throw new SQLException("Invalid data view WHERE condition: " + e.getMessage()); + } finally { + basePlan.close(); + } var aclResult = session.checkACL(sql); if (aclResult != null && (aclResult.getRiskLevel() == Common.RiskLevel.Reject || aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject)) { @@ -145,26 +203,39 @@ public void createDataView(String schemaName, String tableName) { this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); })); - SessionManager.getCurrentSession().getController().showDialog(dialog); + var controller = SessionManager.getCurrentSession().getController(); + DialogHandle dialogHandle = controller.showDialog(dialog, () -> { + hasNext.set(false); + countDownLatch.countDown(); + }); try { - countDownLatch.await(); + if (!countDownLatch.await(WARNING_DIALOG_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + hasNext.set(false); + dialogHandle.cancel(); + } if (!hasNext.get()) { throw new SQLException(MessageUtils.get("ExecutionCanceled")); } } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + hasNext.set(false); this.stateManager.commit(); this.getConsoleLogger().error("get result error"); + throw new SQLException(MessageUtils.get("ExecutionCanceled"), e); } finally { - SessionManager.getCurrentSession().getController().closeDialog(); + dialogHandle.close(); } } - + var plan = this.getDatasource() + .getConnectionManager() + .getSqlActuator() + .createPlan(SQL.of(sql)); plan.setSqlQueryParams(sqlQueryParams); plan.generateTargetSQL(); @@ -181,6 +252,41 @@ public void createDataView(String schemaName, String tableName) { public void onDataViewAction(DataViewAction action) { + if (DataViewAction.ACTION_SAVE_CHANGES_PREVIEW.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + var context = new TableEditContext( + this.tableDataView.getTitle(), + this.schema, + this.table, + this.tableDataView.getData().getFields(), + this.getDatasource().getDruidDbType(), + true + ); + var result = this.tableChangesPreviewService.preview(context, action.getDataView(), request); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, result); + return; + } + if (DataViewAction.ACTION_SAVE_CHANGES.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + var context = new TableEditContext( + this.tableDataView.getTitle(), + this.schema, + this.table, + this.tableDataView.getData().getFields(), + this.getDatasource().getDruidDbType(), + true + ); + var result = this.tableChangesSaveService.save( + context, + action.getDataView(), + request, + new TableBrowseSaveExecutionContext(this.getDatasource().getConnectionManager()), + SessionManager.getCurrentSession() + ); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_RESULT, result); + return; + } + try { this.tableDataView.getStateManager().getState().setLoading(true); this.tableDataView.getStateManager().commit(); @@ -199,5 +305,24 @@ public void onDataViewAction(DataViewAction action) { @Override public void close() { + if (!this.closed.compareAndSet(false, true)) { + return; + } + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null && this.getPacketIO().getWsSession().getAttributes() != null) { + Object token = this.getPacketIO().getWsSession().getAttributes().get("token"); + if (token instanceof String sessionToken) { + currentSession = SessionManager.getSession(sessionToken); + } + } + if (currentSession != null && currentSession.getController() != null) { + currentSession.getController().cancelDialogs(this.getPacketIO().getWsSession().getId()); + } + this.executionLock.lock(); + try { + // Wait for admitted work to observe cancellation and leave the execution section. + } finally { + this.executionLock.unlock(); + } } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java index a3fab59..830e34f 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsole.java @@ -2,73 +2,142 @@ import com.alibaba.druid.sql.parser.ParserException; import com.google.gson.Gson; +import com.google.gson.JsonParseException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.console.action.DataViewAction; import org.jumpserver.chen.framework.console.action.QueryConsoleAction; +import org.jumpserver.chen.framework.console.action.SQLChunkData; import org.jumpserver.chen.framework.console.dataview.DataView; +import org.jumpserver.chen.framework.console.dataview.QueryDataViewTableEditContextFactory; import org.jumpserver.chen.framework.console.dataview.UpdateDataView; +import org.jumpserver.chen.framework.console.context.ConsoleContext; import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; import org.jumpserver.chen.framework.console.entity.response.Message; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesPreviewResult; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesResult; import org.jumpserver.chen.framework.console.state.QueryConsoleState; import org.jumpserver.chen.framework.console.state.StateManager; +import org.jumpserver.chen.framework.console.transaction.QueryTransactionProbeResult; +import org.jumpserver.chen.framework.console.transaction.QueryTransactionStateInspector; import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.edit.ConnectionOwnership; +import org.jumpserver.chen.framework.datasource.edit.SaveExecutionContext; +import org.jumpserver.chen.framework.datasource.edit.ServiceManagedSaveExecutionContext; +import org.jumpserver.chen.framework.datasource.edit.TableChangesPreviewService; +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; +import org.jumpserver.chen.framework.datasource.edit.UserManagedSaveExecutionContext; import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.datasource.sql.SQLActuator; import org.jumpserver.chen.framework.datasource.sql.SQLExecutePlan; +import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; +import org.jumpserver.chen.framework.session.Session; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Button; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; -import org.jumpserver.chen.framework.utils.TreeUtils; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.wisp.Common; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; @Slf4j public class QueryConsole extends AbstractConsole { + private static final String PACKET_SAVE_CHANGES_PREVIEW_RESULT = "save_changes_preview_result"; + private static final String PACKET_SAVE_CHANGES_RESULT = "save_changes_result"; + private static final long WARNING_DIALOG_TIMEOUT_SECONDS = 300; + static final String QUERY_CONSOLE_CONNECTION_UNAVAILABLE = "QUERY_CONSOLE_CONNECTION_UNAVAILABLE"; + static final String QUERY_TRANSACTION_MANUAL_IDLE = "QUERY_TRANSACTION_MANUAL_IDLE"; + static final String QUERY_TRANSACTION_FAILED = "QUERY_TRANSACTION_FAILED"; + static final String QUERY_TRANSACTION_STATE_UNKNOWN = "QUERY_TRANSACTION_STATE_UNKNOWN"; + static final String QUERY_TRANSACTION_PROBE_FAILED = "QUERY_TRANSACTION_PROBE_FAILED"; + static final String QUERY_INSERT_NOT_SUPPORTED = "QUERY_INSERT_NOT_SUPPORTED"; + static final String QUERY_DELETE_NOT_SUPPORTED = "QUERY_DELETE_NOT_SUPPORTED"; + static final String CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED = "CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED"; + private static final String EXECUTION_STATUS_RUNNING = "running"; + private static final String EXECUTION_STATUS_SUCCESS = "success"; + private static final String EXECUTION_STATUS_ERROR = "error"; + private static final String EXECUTION_STATUS_CANCELLED = "cancelled"; private final Datasource datasource; + private final boolean consoleMode; + private final int titleSequence; + private final TableChangesPreviewService tableChangesPreviewService = new TableChangesPreviewService(); + private final TableChangesSaveService tableChangesSaveService = new TableChangesSaveService(); + private final QueryDataViewTableEditContextFactory tableEditContextFactory = new QueryDataViewTableEditContextFactory(); + // Single lifecycle lock: beginExecution() admits work before it can access the connection, + // and close() waits for admitted work to finish. Reentrancy lets the admitted handler call + // getConnection() throughout its execution without observing closed=true midway through. + private final ReentrantLock executionLock = new ReentrantLock(); + private final AtomicBoolean closed = new AtomicBoolean(false); private Connection conn; - private volatile SQLExecutePlan currentPlan; + // Driver-specific transaction probe created alongside the physical connection. The driver's + // cached autoCommit flag does not reflect explicit BEGIN/START TRANSACTION on MySQL, Oracle, + // SQL Server, Dameng or DB2, so the server-side probe is the real source of truth for whether + // a user transaction is open before a DataView save commits or stages behind a savepoint. + private volatile QueryTransactionStateInspector transactionStateInspector; + private volatile ActiveExecution currentExecution; private StateManager stateManager; - private final Map dataViews = new HashMap<>(); + private final Map dataViews = new LinkedHashMap<>(); + // Manual context changes remain restricted to values returned by the current server-side actuator. private volatile Map allowedContexts = Map.of(); private static final Gson GSON = new Gson(); - public QueryConsole(Datasource datasource, WebSocketSession ws, String nodeKey) { - super(datasource, ws, nodeKey); - this.setTitle(String.format(MessageUtils.get("Query") + "-%d", generateConsoleName())); + public QueryConsole(Datasource datasource, WebSocketSession ws, ConsoleContext context) { + this(datasource, ws, context, false); + } + + public QueryConsole( + Datasource datasource, + WebSocketSession ws, + ConsoleContext context, + boolean consoleMode + ) { + super(datasource, ws, context); + this.consoleMode = consoleMode; + this.titleSequence = generateConsoleName(consoleMode); + this.setTitle(String.format( + MessageUtils.get(consoleMode ? "Console" : "Query") + "-%d", + this.titleSequence + )); this.datasource = datasource; } - private static int generateConsoleName() { + private static int generateConsoleName(boolean consoleMode) { int num = 1; var consoles = SessionManager .getCurrentSession() .getConsoles(); for (var console : consoles.values()) { - if (console instanceof QueryConsole) { - ++num; + if (console instanceof QueryConsole queryConsole && queryConsole.consoleMode == consoleMode) { + num = Math.max(num, queryConsole.titleSequence + 1); } } return num; @@ -76,8 +145,15 @@ private static int generateConsoleName() { @Override public void onInit(Connect connect) { - super.onInit(connect); - this.onConnect(connect); + if (!this.beginExecution()) { + return; + } + try { + super.onInit(connect); + this.onConnect(connect); + } finally { + this.executionLock.unlock(); + } } public void onConnect(Connect connect) { @@ -88,7 +164,7 @@ public void onConnect(Connect connect) { this.getState().setLoading(true); this.stateManager.commit(); - var context = TreeUtils.getValue(connect.getNodeKey(), this.getDatasource().getConnectionManager().getContextKey()); + var context = this.getInitialContext(); try { var currentContext = this.getSqlActuator().getCurrentSchema(); @@ -108,6 +184,7 @@ public void onConnect(Connect connect) { } catch (SQLException e) { this.getConsoleLogger().error(MessageUtils.get("ConnectError") + ": %s", e.getMessage()); + throw new IllegalStateException("Failed to initialize query console", e); } this.getState().setLoading(false); @@ -115,27 +192,104 @@ public void onConnect(Connect connect) { } + String getInitialContext() { + var contextKey = this.getDatasource().getConnectionManager().getContextKey(); + return StringUtils.equals(contextKey, "database") + ? this.getContext().database() + : this.getContext().schema(); + } + private Connection getConnection() { - if (this.conn == null) { - try { - this.conn = this.getDatasource().getConnectionManager().getPhysicalConnection(); - } catch (SQLException e) { - throw new RuntimeException(e); + if (!this.executionLock.isHeldByCurrentThread()) { + throw new IllegalStateException("Connection access requires admitted execution"); + } + return this.getOrCreateConnection(); + } + + private Connection getOrCreateConnection() { + if (this.conn != null) { + return this.conn; + } + + Connection candidate = null; + try { + var connectionManager = this.getDatasource().getConnectionManager(); + String currentContext = this.currentConnectionContext(); + if (StringUtils.isNotBlank(currentContext) && + StringUtils.equals( + connectionManager.getContextKey(), + connectionManager.getDatabaseContextKey() + )) { + connectionManager.setDatabaseContext(currentContext); + } + + candidate = connectionManager.getPhysicalConnection(); + if (StringUtils.isNotBlank(currentContext)) { + connectionManager.getSqlActuator() + .withConnection(candidate) + .changeSchema(currentContext); } + QueryTransactionStateInspector candidateInspector = QueryTransactionStateInspector.create( + this.getDatasource().getDruidDbType(), + candidate + ); + + this.conn = candidate; + this.transactionStateInspector = candidateInspector; + return candidate; + } catch (SQLException e) { + closeQuietly(candidate); + throw new QueryConsoleConnectionUnavailableException(e); + } catch (RuntimeException e) { + closeQuietly(candidate); + throw e; } - return this.conn; } + private String currentConnectionContext() { + if (this.stateManager == null) { + return null; + } + return this.getState().getCurrentContext(); + } + + public String getCurrentContext() { + return this.stateManager == null ? "" : StringUtils.defaultString(this.getState().getCurrentContext()); + } @Override public void handle(Packet packet) { + if (this.isCancelPacket(packet)) { + this.onCancel(); + return; + } + if (!this.beginExecution()) { + return; + } + try { + this.handleSerialPacket(packet); + } catch (QueryConsoleConnectionUnavailableException e) { + log.warn("query console connection unavailable", e); + this.getMessager().send(Message.error( + MessageUtils.get("FetchError"), + QUERY_CONSOLE_CONNECTION_UNAVAILABLE + )); + } finally { + this.executionLock.unlock(); + } + } + + private void handleSerialPacket(Packet packet) { switch (packet.getType()) { case "ping" -> this.getPacketIO().sendPacket("pong", null); case "close_data_view" -> { - var name = (String) packet.getData(); - this.dataViews.remove(name); - log.info("close data view {}", name); + var reference = (String) packet.getData(); + var dataView = this.findDataView(reference); + if (dataView != null) { + this.dataViews.remove(dataView.getId()); + } + log.info("close data view {}", reference); } case Packet.TYPE_QUERY_CONSOLE_ACTION -> { @@ -151,6 +305,26 @@ public void handle(Packet packet) { } } + private boolean isCancelPacket(Packet packet) { + if (!StringUtils.equals(packet.getType(), Packet.TYPE_QUERY_CONSOLE_ACTION)) { + return false; + } + var action = GSON.fromJson(GSON.toJson(packet.getData()), QueryConsoleAction.class); + return StringUtils.equals(action.getAction(), QueryConsoleAction.ACTION_CANCEL); + } + + private boolean beginExecution() { + if (this.closed.get()) { + return false; + } + this.executionLock.lock(); + if (this.closed.get()) { + this.executionLock.unlock(); + return false; + } + return true; + } + private void onAction(QueryConsoleAction action) { switch (action.getAction()) { @@ -182,12 +356,6 @@ private void onAction(QueryConsoleAction action) { this.stateManager.commit(); } - - case QueryConsoleAction.ACTION_CANCEL -> { - this.onCancel(); - this.getState().setInQuery(false); - this.stateManager.commit(); - } case QueryConsoleAction.ACTION_CHANGE_CURRENT_CONTEXT -> { var schema = (String) action.getData(); this.onManualChangeContext(schema); @@ -195,26 +363,40 @@ private void onAction(QueryConsoleAction action) { } } - private final ConcurrentHashMap sqlChunks = new ConcurrentHashMap<>(); - private CountDownLatch latch; + private final Map sqlChunks = new HashMap<>(); private int expectedChunks = -1; private void handleSQLChunk(QueryConsoleAction action) { - var data = (Map) action.getData(); - var chunk = (String) data.get("chunk"); - var index = (Integer) data.get("index"); - var total = (Integer) data.get("total"); - - synchronized (this) { - if (expectedChunks == -1) { - expectedChunks = total; - latch = new CountDownLatch(total); - } + SQLChunkData data; + try { + data = GSON.fromJson(GSON.toJson(action.getData()), SQLChunkData.class); + } catch (JsonParseException e) { + this.getConsoleLogger().error("invalid sql chunk"); + this.resetSQLChunks(); + return; } - if (sqlChunks.putIfAbsent(index, chunk) == null) { - latch.countDown(); + if (data == null + || data.getChunk() == null + || data.getIndex() == null + || data.getTotal() == null + || data.getTotal() <= 0) { + this.getConsoleLogger().error("invalid sql chunk"); + this.resetSQLChunks(); + return; + } + var chunk = data.getChunk(); + var index = data.getIndex(); + var total = data.getTotal(); + if (expectedChunks == -1) { + expectedChunks = total; + } + if (total != expectedChunks || index < 0 || index >= expectedChunks) { + this.getConsoleLogger().error("invalid sql chunk"); + this.resetSQLChunks(); + return; } + sqlChunks.putIfAbsent(index, chunk); } /** @@ -222,11 +404,7 @@ private void handleSQLChunk(QueryConsoleAction action) { */ private void handleSQLComplete() { try { - - // 等待所有分段接收完成 - boolean completed = latch.await(10, TimeUnit.SECONDS); // 超时10秒 - - if (!completed) { + if (expectedChunks <= 0 || sqlChunks.size() != expectedChunks) { this.getConsoleLogger().error("read sql message timeout!!"); return; } @@ -234,13 +412,15 @@ private void handleSQLComplete() { // 按照索引顺序合并所有分段 StringBuilder sqlBuilder = new StringBuilder(); for (int i = 0; i < expectedChunks; i++) { - sqlBuilder.append(sqlChunks.get(i)); + String chunk = sqlChunks.get(i); + if (chunk == null) { + this.getConsoleLogger().error("read sql message timeout!!"); + return; + } + sqlBuilder.append(chunk); } - // 合并完成后清理缓存 var sql = sqlBuilder.toString(); - sqlChunks.clear(); - expectedChunks = -1; // 执行完整 SQL this.getState().setInQuery(true); @@ -248,27 +428,77 @@ private void handleSQLComplete() { this.onSQL(sql); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); } finally { + this.resetSQLChunks(); this.getState().setInQuery(false); this.stateManager.commit(); } } + private void resetSQLChunks() { + this.sqlChunks.clear(); + this.expectedChunks = -1; + } + private void onDataViewAction(DataViewAction action) { - var dataView = this.dataViews.get(action.getDataView()); + if (this.consoleMode && DataViewAction.ACTION_SAVE_CHANGES_PREVIEW.equals(action.getAction())) { + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, Map.of( + "success", false, + "allowed", false, + "reason", CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED, + "dataView", action.getDataView() + )); + return; + } + if (this.consoleMode && DataViewAction.ACTION_SAVE_CHANGES.equals(action.getAction())) { + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_RESULT, Map.of( + "success", false, + "allowed", false, + "reason", CONSOLE_DATA_VIEW_EDIT_NOT_SUPPORTED, + "dataView", action.getDataView() + )); + return; + } + var dataView = this.findDataView(action.getDataView()); if (dataView == null) { log.error("data view {} not found", action.getDataView()); return; } + if (DataViewAction.ACTION_SAVE_CHANGES_PREVIEW.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + String unsupportedReason = unsupportedQueryMutation(request); + if (unsupportedReason != null) { + this.getPacketIO().sendPacket( + PACKET_SAVE_CHANGES_PREVIEW_RESULT, + this.rejectedPreview(dataView, unsupportedReason) + ); + return; + } + try { + var context = this.tableEditContextFactory.create(dataView, this.getDatasource().getDruidDbType()); + var result = this.tableChangesPreviewService.preview(context, dataView.getTitle(), request); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, result); + } catch (IllegalArgumentException e) { + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_PREVIEW_RESULT, this.rejectedPreview(dataView, e.getMessage())); + } + return; + } + if (DataViewAction.ACTION_SAVE_CHANGES.equals(action.getAction())) { + var request = GSON.fromJson(GSON.toJson(action.getData()), SaveChangesRequest.class); + SaveChangesResult result = this.saveQueryChanges(dataView, dataView.getTitle(), request); + this.getPacketIO().sendPacket(PACKET_SAVE_CHANGES_RESULT, result); + return; + } try { dataView.getStateManager().getState().setLoading(true); dataView.getStateManager().commit(); dataView.doAction(action); - this.getPacketIO().sendPacket("update_data_view", new UpdateDataView(action.getDataView(), dataView.getData())); + this.getPacketIO().sendPacket( + "update_data_view", + new UpdateDataView(dataView.getId(), dataView.getTitle(), dataView.getData()) + ); } catch (SQLException e) { this.getMessager().send(Message.error(MessageUtils.get("FetchError"), e.getMessage())); @@ -278,13 +508,139 @@ private void onDataViewAction(DataViewAction action) { } } + private SaveChangesResult saveQueryChanges( + DataView dataView, + String actionDataView, + SaveChangesRequest request + ) { + String unsupportedReason = unsupportedQueryMutation(request); + if (unsupportedReason != null) { + return this.rejectedSave(dataView, unsupportedReason); + } + + // Probe the driver-side transaction state before touching the connection. The cached + // autoCommit flag is unreliable on MySQL/Oracle/SQL Server/Dameng/DB2 (it does not flip on + // explicit BEGIN), so the server-side probe is the source of truth: + // AUTO_COMMIT -> no user tx open; service-managed transaction committed for the user + // TRANSACTION_ACTIVE -> user owns the tx; batch staged behind a savepoint, outer tx never committed + // MANUAL_COMMIT_IDLE -> reject; Chen does not start a user transaction implicitly + // TRANSACTION_FAILED -> reject; the user must rollback the failed tx first + // UNKNOWN / probeFailed -> fail closed; never risk committing or staging on an uncertain connection + Connection connection = this.getConnection(); + QueryTransactionStateInspector inspector = this.transactionStateInspector; + if (inspector == null) { + return this.rejectedSave(dataView, QUERY_CONSOLE_CONNECTION_UNAVAILABLE); + } + QueryTransactionProbeResult probeResult = inspector.probeNow(); + if (probeResult.probeFailed()) { + return this.rejectedSave(dataView, QUERY_TRANSACTION_PROBE_FAILED); + } + + SaveExecutionContext executionContext; + switch (probeResult.state()) { + case AUTO_COMMIT -> executionContext = new ServiceManagedSaveExecutionContext( + connection, + ConnectionOwnership.QUERY_CONSOLE + ); + case TRANSACTION_ACTIVE -> executionContext = new UserManagedSaveExecutionContext(connection); + case MANUAL_COMMIT_IDLE -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_MANUAL_IDLE); + } + case TRANSACTION_FAILED -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_FAILED); + } + case UNKNOWN -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_STATE_UNKNOWN); + } + default -> { + return this.rejectedSave(dataView, QUERY_TRANSACTION_STATE_UNKNOWN); + } + } + + try { + var context = this.tableEditContextFactory.create( + dataView, + this.getDatasource().getDruidDbType() + ); + SaveChangesResult result = this.tableChangesSaveService.save( + context, + actionDataView, + request, + executionContext, + SessionManager.getCurrentSession() + ); + if (result.isConnectionInvalidated()) { + this.invalidateConnection(result.getReason()); + } + return result; + } catch (IllegalArgumentException e) { + return this.rejectedSave(dataView, e.getMessage()); + } + } + + private void invalidateConnection(String reason) { + closeQuietly(this.detachConnection()); + log.warn("QueryConsole connection invalidated, reason={}", reason); + } + + private Connection detachConnection() { + Connection old = this.conn; + this.conn = null; + this.transactionStateInspector = null; + return old; + } + + private static void closeQuietly(Connection connection) { + if (connection == null) { + return; + } + try { + connection.close(); + } catch (SQLException | RuntimeException e) { + log.warn("close query console connection failed", e); + } + } + + private static String unsupportedQueryMutation(SaveChangesRequest request) { + if (request == null) { + return null; + } + if (request.getInsertRows() != null && !request.getInsertRows().isEmpty()) { + return QUERY_INSERT_NOT_SUPPORTED; + } + if (request.getDeleteRows() != null && !request.getDeleteRows().isEmpty()) { + return QUERY_DELETE_NOT_SUPPORTED; + } + return null; + } + + private SaveChangesPreviewResult rejectedPreview(DataView dataView, String reason) { + SaveChangesPreviewResult result = new SaveChangesPreviewResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setReason(reason); + result.setDataView(dataView.getId()); + return result; + } + + private SaveChangesResult rejectedSave(DataView dataView, String reason) { + SaveChangesResult result = new SaveChangesResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setReason(reason); + result.setDataView(dataView.getId()); + return result; + } + public void onCancel() { + this.getState().setExecutionStatus(EXECUTION_STATUS_CANCELLED); try { - if (this.currentPlan != null) { - this.currentPlan.cancel(); - this.getConsoleLogger().error("cancel query: %s", this.currentPlan.getTargetSQL()); + var execution = this.currentExecution; + if (execution != null) { + execution.cancelAction().cancel(); + this.getConsoleLogger().warn("cancel query: %s", execution.sql()); } - } catch (SQLException e) { + } catch (SQLException | RuntimeException e) { log.error("cancel failed ", e); } } @@ -302,6 +658,12 @@ public void onManualChangeContext(String context) { this.stateManager.commit(); this.getSqlActuator().changeSchema(allowedContext); + var connectionManager = this.getDatasource().getConnectionManager(); + // 只有当前 UI 上下文本身就是 JDBC database 时,才同步连接池上下文,避免 PostgreSQL schema 被当 database。 + if (StringUtils.isNotBlank(allowedContext) && + StringUtils.equals(connectionManager.getContextKey(), connectionManager.getDatabaseContextKey())) { + connectionManager.setDatabaseContext(allowedContext); + } this.getState().setCurrentContext(allowedContext); } catch (SQLException e) { @@ -330,120 +692,257 @@ boolean isAllowedContext(String context) { } public void onSQLFile(String filename) { - if (StringUtils.isBlank(filename) - || filename.contains("/") - || filename.contains("\\") - || filename.contains("..")) { + var filePath = this.resolveSQLFileInSessionTemp(filename); + if (filePath == null) { log.warn("Rejected invalid SQL file name"); return; } - var filePath = SessionManager.getCurrentSession().getTempPath().resolve(filename); - var file = filePath.toFile(); - - if (!file.exists()) { + if (!Files.exists(filePath, LinkOption.NOFOLLOW_LINKS)) { this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_not_found"), filename); return; } - if (!file.isFile()) { + if (!Files.isRegularFile(filePath, LinkOption.NOFOLLOW_LINKS)) { this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_not_file"), filename); return; } - if (!file.canRead()) { + if (!Files.isReadable(filePath)) { this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_not_readable"), filename); return; } + var shouldDelete = false; try { - var sql = Files.readString(file.toPath()); + shouldDelete = true; + String sql; + try { + try (var inputStream = Files.newInputStream( + filePath, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) { + sql = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + } catch (IOException | SecurityException e) { + this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_read_error"), e.getMessage()); + return; + } this.onSQL(sql); - } catch (IOException e) { - this.getConsoleLogger().error("%s: %s", MessageUtils.get("msg.error.file_read_error"), e.getMessage()); } finally { - file.delete(); + if (shouldDelete) { + try { + Files.deleteIfExists(filePath); + } catch (IOException | SecurityException e) { + log.warn("Failed to delete session SQL file", e); + } + } + } + } + + private Path resolveSQLFileInSessionTemp(String filename) { + if (StringUtils.isBlank(filename) + || StringUtils.equalsAny(filename, ".", "..") + || filename.contains("..") + || filename.contains("/") + || filename.contains("\\")) { + return null; + } + + try { + var requested = Path.of(filename); + if (requested.isAbsolute() + || requested.getNameCount() != 1 + || !requested.equals(requested.getFileName())) { + return null; + } + + var basePath = SessionManager.getCurrentSession().getTempPath() + .toAbsolutePath() + .normalize(); + var resolvedPath = basePath.resolve(requested.getFileName()).normalize(); + return resolvedPath.startsWith(basePath) ? resolvedPath : null; + } catch (InvalidPathException | SecurityException e) { + return null; } } public void onSQL(String sql) { this.getState().setInQuery(true); + this.getState().setExecutionStatus(EXECUTION_STATUS_RUNNING); this.stateManager.commit(); var session = SessionManager.getCurrentSession(); - var aclResult = session.checkACL(sql, this.getConnection()); - if (aclResult != null) { - if (aclResult.getRiskLevel() == Common.RiskLevel.Reject || aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject) { - this.getConsoleLogger().error("%s", MessageUtils.get("ACLRejectError")); - CommandRecord commandRecord = new CommandRecord(sql); - commandRecord.setRiskLevel(aclResult.getRiskLevel()); - session.recordCommand(commandRecord); - - this.getState().setInQuery(false); - this.stateManager.commit(); - return; + try { + if (this.consoleMode) { + this.runRawConsoleSQL(sql, session); + } else { + this.runQuerySQL(sql, session); } + this.ensureCurrentSchema(); + } catch (ParserException e) { + this.getState().setExecutionStatus(EXECUTION_STATUS_ERROR); + this.getConsoleLogger().error("%s: %s", MessageUtils.get("ParseError"), e.getMessage()); + this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ParseError"), e.getMessage())); + this.sendSQLError("parse", MessageUtils.get("ParseError"), e.getMessage(), sql, null); + } catch (SQLException e) { + if (!StringUtils.equals(this.getState().getExecutionStatus(), EXECUTION_STATUS_CANCELLED)) { + this.getState().setExecutionStatus(EXECUTION_STATUS_ERROR); + this.getConsoleLogger().error("%s: %s", MessageUtils.get("ExecuteError"), e.getMessage()); + this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ExecuteError"), e.getMessage())); + this.sendSQLError("execute", MessageUtils.get("ExecuteError"), e.getMessage(), sql, e); + } + } finally { + if (StringUtils.equals(this.getState().getExecutionStatus(), EXECUTION_STATUS_RUNNING)) { + this.getState().setExecutionStatus(EXECUTION_STATUS_SUCCESS); + } + this.getState().setInQuery(false); + this.getState().setCanCancel(false); + this.stateManager.commit(); + } + } - if (aclResult.isNotify()) { - - var dialog = new Dialog(MessageUtils.get("Warning")); - dialog.setBody(MessageUtils.get("CommandWarningDialogMessage")); - var countDownLatch = new CountDownLatch(1); - AtomicBoolean hasNext = new AtomicBoolean(true); - - dialog.addButton(new Button(MessageUtils.get("Submit"), "submit", countDownLatch::countDown)); + private void runQuerySQL(String sql, Session session) throws SQLException { + var statements = this.getSqlActuator().parseSQL(SQL.of(sql)); + var clearOthers = true; + for (String statement : statements) { + var aclResult = session.checkACL(statement, this.getConnection()); + if (!this.canExecuteStatement(session, statement, aclResult)) { + break; + } + var dataView = this.runSingleSQL(statement, aclResult); + if (!dataView.isHasTable()) { + this.logAffectedRows(dataView); + } else { + this.sendDataView(dataView, clearOthers); + clearOthers = false; + } + } + } - dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { - hasNext.set(false); - countDownLatch.countDown(); - this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); - })); + private void runRawConsoleSQL(String sql, Session session) throws SQLException { + ConsoleStatementBoundaryScanner.requireSingleStatement(sql, this.datasource.getDruidDbType()); + Connection connection = this.getConnection(); + ACLResult aclResult = session.checkACL(sql, connection); + if (!this.canExecuteStatement(session, sql, aclResult)) { + return; + } - SessionManager.getCurrentSession().getController().showDialog(dialog); + SQLQueryResult executionResult = this.executeRawConsoleSQL(sql, aclResult, connection); + if (executionResult.getResults().isEmpty()) { + this.getConsoleLogger().success("%s", MessageUtils.get("ExecuteSuccess")); + } + for (SQLQueryResult result : executionResult.getResults()) { + this.getConsoleLogger().success(result); + if (!result.isHasResultSet()) { + continue; + } - try { - countDownLatch.await(); + DataView dataView = new DataView( + UUID.randomUUID().toString(), sql, this.getPacketIO(), this.getConsoleLogger() + ); + dataView.setSql(sql); + dataView.setLoadDataInterface((ignored) -> result); + dataView.loadData(); + this.sendDataView(dataView, false); + } + } - if (!hasNext.get()) { - this.getState().setInQuery(false); - this.stateManager.commit(); - return; - } + private SQLQueryResult executeRawConsoleSQL(String sql, ACLResult aclResult, Connection connection) + throws SQLException { + SQLActuator actuator = this.datasource.getConnectionManager().getSqlActuator().withConnection(connection); + SQLExecutePlan plan = actuator.createPlan(SQL.of(sql)); + plan.setAclResult(aclResult); + Statement statement = plan.createStatement(); + ActiveExecution execution = new ActiveExecution(sql, statement::cancel); + this.currentExecution = execution; + this.getState().setCanCancel(true); + this.stateManager.commit(); + try { + this.getConsoleLogger().info("execute sql: %s", sql); + return actuator.executeRawWithAudit(plan); + } finally { + if (this.currentExecution == execution) { + this.currentExecution = null; + } + this.getState().setCanCancel(false); + this.stateManager.commit(); + } + } - } catch (InterruptedException e) { - this.getState().setInQuery(false); - this.stateManager.commit(); + private void logAffectedRows(DataView dataView) { + this.getConsoleLogger().success("%s , %s: %d", + MessageUtils.get("ExecuteSuccess"), + MessageUtils.get("AffectedRows"), dataView.getUpdateCount()); + } - this.getConsoleLogger().error("获取结果失败!"); - } finally { - SessionManager.getCurrentSession().getController().closeDialog(); - } + private void sendSQLError(String kind, String title, String message, String sql, SQLException exception) { + var error = new LinkedHashMap(); + error.put("kind", kind); + error.put("title", StringUtils.defaultString(title)); + error.put("message", StringUtils.defaultString(message)); + error.put("sql", StringUtils.defaultString(sql)); + error.put("timestamp", System.currentTimeMillis()); + if (exception != null) { + if (StringUtils.isNotBlank(exception.getSQLState())) { + error.put("sqlState", exception.getSQLState()); } + error.put("vendorCode", exception.getErrorCode()); } + this.getPacketIO().sendPacket("sql_error", error); + } + private boolean canExecuteStatement(Session session, String sql, ACLResult aclResult) { + if (aclResult == null) { + return true; + } + if (aclResult.getRiskLevel() == Common.RiskLevel.Reject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewCancel) { + this.getState().setExecutionStatus( + aclResult.getRiskLevel() == Common.RiskLevel.ReviewCancel + ? EXECUTION_STATUS_CANCELLED + : EXECUTION_STATUS_ERROR + ); + this.getConsoleLogger().error("%s", MessageUtils.get("ACLRejectError")); + CommandRecord commandRecord = new CommandRecord(sql); + commandRecord.setRiskLevel(aclResult.getRiskLevel()); + session.recordCommand(commandRecord); + return false; + } + return !aclResult.isNotify() || this.confirmStatementWarning(session); + } + + private boolean confirmStatementWarning(Session session) { + var dialog = new Dialog(MessageUtils.get("Warning")); + dialog.setBody(MessageUtils.get("CommandWarningDialogMessage")); + var countDownLatch = new CountDownLatch(1); + AtomicBoolean hasNext = new AtomicBoolean(true); + + dialog.addButton(new Button(MessageUtils.get("Submit"), "submit", countDownLatch::countDown)); + dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { + hasNext.set(false); + this.getState().setExecutionStatus(EXECUTION_STATUS_CANCELLED); + countDownLatch.countDown(); + this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); + })); + + var controller = session.getController(); + DialogHandle dialogHandle = controller.showDialog(dialog, () -> { + hasNext.set(false); + countDownLatch.countDown(); + }); try { - var stmts = this.getSqlActuator().parseSQL(SQL.of(sql)); - var clearOthers = true; - for (String stmt : stmts) { - var dataView = this.runSingleSQL(stmt, aclResult); - if (!dataView.isHasTable()) { - this.getConsoleLogger().success("%s , %s: %d", - MessageUtils.get("ExecuteSuccess"), - MessageUtils.get("AffectedRows"), dataView.getUpdateCount()); - } else { - this.sendDataView(dataView, clearOthers); - clearOthers = false; - } + if (!countDownLatch.await(WARNING_DIALOG_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + hasNext.set(false); + this.getState().setExecutionStatus(EXECUTION_STATUS_CANCELLED); + dialogHandle.cancel(); + this.getConsoleLogger().warn(MessageUtils.get("ExecutionCanceled")); } - this.ensureCurrentSchema(); - } catch (ParserException e) { - this.getConsoleLogger().error("%s: %s", MessageUtils.get("ParseError"), e.getMessage()); - this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ParseError"), e.getMessage())); - } catch (SQLException e) { - this.getConsoleLogger().error("%s: %s", MessageUtils.get("ExecuteError"), e.getMessage()); - this.getPacketIO().sendPacket("message", Message.error(MessageUtils.get("ExecuteError"), e.getMessage())); + return hasNext.get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + this.getState().setExecutionStatus(EXECUTION_STATUS_ERROR); + this.getConsoleLogger().error("获取结果失败!"); + return false; } finally { - this.getState().setInQuery(false); - this.getState().setCanCancel(false); - this.stateManager.commit(); + dialogHandle.close(); } } @@ -474,78 +973,150 @@ private void ensureCurrentSchema() { private DataView runSingleSQL(String sql, ACLResult aclResult) throws SQLException { - SQLExecutePlan plan = this.datasource - .getConnectionManager() - .getSqlActuator() - .withConnection(this.getConnection()) - .createPlan(SQL.of(sql)); - - plan.setAclResult(aclResult); - DataView dataView = new DataView(plan.getSourceSQL(), this.getPacketIO(), this.getConsoleLogger()); - dataView.setSql(plan.getSourceSQL()); + String sourceSQL = sql; + DataView dataView = new DataView( + UUID.randomUUID().toString(), + sourceSQL, + this.getPacketIO(), + this.getConsoleLogger() + ); + dataView.setSql(sourceSQL); dataView.setLoadDataInterface((sqlQueryParams) -> { sqlQueryParams.setTimeout(this.getState().getTimeout()); + SQLExecutePlan plan = this.datasource + .getConnectionManager() + .getSqlActuator() + .withConnection(this.getConnection()) + .createPlan(SQL.of(sourceSQL)); + plan.setAclResult(aclResult); plan.setSqlQueryParams(sqlQueryParams); - plan.generateTargetSQL(); - - this.getConsoleLogger().info("execute sql: %s", plan.getTargetSQL()); - - this.currentPlan = plan; - + ActiveExecution execution = new ActiveExecution(sourceSQL, plan::cancel); + this.currentExecution = execution; this.getState().setCanCancel(true); this.stateManager.commit(); - var result = plan.executeWithAudit(); - this.currentPlan = null; - - this.getConsoleLogger().success(result); - return result; + try { + plan.generateTargetSQL(); + this.getConsoleLogger().info("execute sql: %s", plan.getTargetSQL()); + var result = plan.executeWithAudit(); + this.getConsoleLogger().success(result); + return result; + } finally { + if (this.currentExecution == execution) { + this.currentExecution = null; + } + this.getState().setCanCancel(false); + this.stateManager.commit(); + } }); dataView.loadData(); - this.getState().setCanCancel(false); - this.stateManager.commit(); - return dataView; } private void sendDataView(DataView dataView, boolean clearOthers) { + if (this.consoleMode) { + this.getPacketIO().sendPacket("console_result", Map.of( + "id", dataView.getId(), + "title", dataView.getTitle(), + "data", dataView.getData(), + "state", dataView.getStateManager().getState() + )); + return; + } + if (clearOthers) { - var forDeleteDataViewTitles = new ArrayList(); - for (var title : this.dataViews.keySet()) { - if (!dataView.getTitle().equals(title) && !this.dataViews.get(title).getStateManager().getState().isPinned()) { - forDeleteDataViewTitles.add(title); + var forDeleteDataViewIds = new ArrayList(); + for (var entry : this.dataViews.entrySet()) { + if (!entry.getValue().getStateManager().getState().isPinned()) { + forDeleteDataViewIds.add(entry.getKey()); } } - forDeleteDataViewTitles.forEach(this.dataViews.keySet()::remove); - this.getPacketIO().sendPacket("close_data_view", forDeleteDataViewTitles); + forDeleteDataViewIds.forEach(this.dataViews.keySet()::remove); + this.getPacketIO().sendPacket("close_data_view", forDeleteDataViewIds); } - if (!this.dataViews.containsKey(dataView.getTitle())) { - this.getPacketIO().sendPacket("new_data_view", Map.of("title", dataView.getTitle())); - } + this.getPacketIO().sendPacket( + "new_data_view", + Map.of("id", dataView.getId(), "title", dataView.getTitle()) + ); - this.dataViews.put(dataView.getTitle(), dataView); - this.getPacketIO().sendPacket("update_data_view", new UpdateDataView(dataView.getTitle(), dataView.getData())); + this.dataViews.put(dataView.getId(), dataView); + this.getPacketIO().sendPacket( + "update_data_view", + new UpdateDataView(dataView.getId(), dataView.getTitle(), dataView.getData()) + ); dataView.getStateManager().commit(); } + private DataView findDataView(String reference) { + var dataView = this.dataViews.get(reference); + if (dataView != null) { + return dataView; + } + DataView matched = null; + for (var candidate : this.dataViews.values()) { + if (StringUtils.equals(candidate.getTitle(), reference)) { + matched = candidate; + } + } + return matched; + } + @Override public void close() { - if (this.currentPlan != null) { - // flush - var session = SessionManager.getCurrentSession(); - var lastCmd = this.currentPlan.getTargetSQL(); - var cmdRecord = new CommandRecord(lastCmd); - cmdRecord.setError("Abnormal exit"); - session.recordCommand(cmdRecord); + if (!this.closed.compareAndSet(false, true)) { + return; + } + + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null && this.getPacketIO().getWsSession().getAttributes() != null) { + Object token = this.getPacketIO().getWsSession().getAttributes().get("token"); + if (token instanceof String sessionToken) { + currentSession = SessionManager.getSession(sessionToken); + } + } + if (currentSession != null && currentSession.getController() != null) { + currentSession.getController().cancelDialogs(this.getPacketIO().getWsSession().getId()); + } + + var execution = this.currentExecution; + if (execution != null) { + try { + // flush + var session = SessionManager.getCurrentSession(); + var lastCmd = execution.sql(); + var cmdRecord = new CommandRecord(lastCmd); + cmdRecord.setError("Abnormal exit"); + if (session != null) { + session.recordCommand(cmdRecord); + } + } catch (RuntimeException e) { + log.warn("record interrupted query failed", e); + } + } + this.onCancel(); + + this.executionLock.lock(); + try { + closeQuietly(this.detachConnection()); + } finally { + this.executionLock.unlock(); } log.info("console closed"); } + + @FunctionalInterface + private interface ExecutionCancel { + void cancel() throws SQLException; + } + + private record ActiveExecution(String sql, ExecutionCancel cancelAction) { + } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsoleConnectionUnavailableException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsoleConnectionUnavailableException.java new file mode 100644 index 0000000..68a244d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/QueryConsoleConnectionUnavailableException.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console; + +final class QueryConsoleConnectionUnavailableException extends RuntimeException { + QueryConsoleConnectionUnavailableException(Throwable cause) { + super("Query console connection is unavailable", cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java index 06d82e2..a1378d9 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/DataViewAction.java @@ -14,6 +14,9 @@ public class DataViewAction extends Action { public static final String ACTION_REFRESH = "refresh"; public static final String ACTION_TOGGLE_PINNED = "toggle_pinned"; public static final String ACTION_CHANGE_LIMIT = "change_limit"; + public static final String ACTION_CHANGE_FILTER = "change_filter"; public static final String ACTION_EXPORT = "export"; + public static final String ACTION_SAVE_CHANGES_PREVIEW = "save_changes_preview"; + public static final String ACTION_SAVE_CHANGES = "save_changes"; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/SQLChunkData.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/SQLChunkData.java new file mode 100644 index 0000000..d42aa25 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/action/SQLChunkData.java @@ -0,0 +1,10 @@ +package org.jumpserver.chen.framework.console.action; + +import lombok.Data; + +@Data +public class SQLChunkData { + private String chunk; + private Integer index; + private Integer total; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContext.java new file mode 100644 index 0000000..18c20a1 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContext.java @@ -0,0 +1,10 @@ +package org.jumpserver.chen.framework.console.context; + +public record ConsoleContext( + String nodeKey, + String nodeType, + String database, + String schema, + String table +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolutionException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolutionException.java new file mode 100644 index 0000000..187936c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolutionException.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console.context; + +public class ConsoleContextResolutionException extends IllegalArgumentException { + public ConsoleContextResolutionException(String message) { + super(message); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolver.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolver.java new file mode 100644 index 0000000..611cb78 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/context/ConsoleContextResolver.java @@ -0,0 +1,45 @@ +package org.jumpserver.chen.framework.console.context; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.console.entity.request.Connect; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; + +import java.util.Set; + +public class ConsoleContextResolver { + private static final Set QUERY_NODE_TYPES = Set.of("datasource", "database", "schema", "table"); + private static final Set DATA_VIEW_NODE_TYPES = Set.of("table", "view"); + + private final ResourceBrowser resourceBrowser; + + public ConsoleContextResolver(ResourceBrowser resourceBrowser) { + this.resourceBrowser = resourceBrowser; + } + + public ConsoleContext resolve(String submittedNodeKey, String consoleType) { + if (StringUtils.isBlank(submittedNodeKey) || StringUtils.isBlank(consoleType)) { + throw new ConsoleContextResolutionException("Invalid console context"); + } + + var node = this.resourceBrowser.getIndexedNode(submittedNodeKey); + if (node == null || !isAllowedNodeType(consoleType, node.type())) { + throw new ConsoleContextResolutionException("Invalid console context"); + } + + return new ConsoleContext( + node.key(), + node.type(), + node.database(), + node.schema(), + node.table() + ); + } + + private static boolean isAllowedNodeType(String consoleType, String nodeType) { + return switch (consoleType) { + case Connect.CONSOLE_TYPE_QUERY, Connect.CONSOLE_TYPE_CONSOLE -> QUERY_NODE_TYPES.contains(nodeType); + case Connect.CONSOLE_TYPE_DATA_VIEW -> DATA_VIEW_NODE_TYPES.contains(nodeType); + default -> false; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java index b9f4af3..11c1d19 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataView.java @@ -8,6 +8,7 @@ import org.jumpserver.chen.framework.console.entity.response.SQLResult; import org.jumpserver.chen.framework.console.state.DataViewState; import org.jumpserver.chen.framework.console.state.StateManager; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; import org.jumpserver.chen.framework.datasource.sql.SQLQueryParams; import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; import org.jumpserver.chen.framework.i18n.MessageUtils; @@ -25,6 +26,7 @@ @EqualsAndHashCode(callSuper = true) @Data public class DataView extends SQLResult { + private final String id; private final String title; private final StateManager stateManager; private LoadDataInterface loadDataInterface; @@ -37,8 +39,13 @@ public class DataView extends SQLResult { private Logger consoleLogger; public DataView(String title, PacketIO packetIO, Logger logger) { + this(title, title, packetIO, logger); + } + + public DataView(String id, String title, PacketIO packetIO, Logger logger) { + this.id = id; this.title = title; - this.state = new DataViewState(title); + this.state = new DataViewState(this.id, title); this.stateManager = new StateManager<>(this.state, packetIO); this.consoleLogger = logger; } @@ -67,6 +74,9 @@ public void doAction(DataViewAction action) throws SQLException { case DataViewAction.ACTION_CHANGE_LIMIT -> { this.changeLimit(this.parseLimit(action.getData())); } + case DataViewAction.ACTION_CHANGE_FILTER -> { + this.changeFilter(this.parseFilter(action.getData())); + } case DataViewAction.ACTION_EXPORT -> { var data = (Map) action.getData(); var scope = data.get("scope"); @@ -90,10 +100,22 @@ private int parseLimit(Object data) throws SQLException { return limit; } + private String parseFilter(Object data) throws SQLException { + if (!(data instanceof String filter)) { + throw new SQLException("Invalid data view filter"); + } + filter = filter.trim(); + if (filter.length() > 10000) { + throw new SQLException("Data view filter is too long"); + } + return filter; + } + public void loadData() throws SQLException { SQLQueryParams queryParams = new SQLQueryParams(); queryParams.setLimit(this.state.getLimit()); queryParams.setOffset((this.state.getPage() - 1) * this.state.getLimit()); + queryParams.setFilter(this.state.getFilter()); var result = this.loadDataInterface .loadData(queryParams); @@ -109,6 +131,8 @@ private void fullData(SQLQueryResult result) { } this.state.setPaged(result.isPaged()); + this.state.setTruncated(result.isTruncated()); + this.state.setRowLimit(result.getRowLimit()); this.data.getFields().clear(); this.data.getData().clear(); @@ -120,7 +144,11 @@ private void fullData(SQLQueryResult result) { private void fullDataViewData(DataViewData viewData, SQLQueryResult result) { - viewData.setFields(result.getFields()); + List fields = result.getFields(); + boolean editable = fields.stream() + .anyMatch(Field::isEditable); + viewData.setEditable(editable); + viewData.setFields(fields); Map fieldNumMap = new HashMap<>(); @@ -170,6 +198,7 @@ public void export(String scope, String format) throws SQLException { case "all": SQLQueryParams queryParams = new SQLQueryParams(); queryParams.setLimit(-1); + queryParams.setFilter(this.state.getFilter()); var result = this.loadDataInterface.loadData(queryParams); var viewData = new DataViewData(); this.fullDataViewData(viewData, result); @@ -251,6 +280,20 @@ public void changeLimit(int limit) throws SQLException { } } + public void changeFilter(String filter) throws SQLException { + var oldFilter = this.state.getFilter(); + var oldPage = this.state.getPage(); + try { + this.state.setFilter(filter); + this.state.setPage(1); + this.loadData(); + } catch (SQLException e) { + this.state.setFilter(oldFilter); + this.state.setPage(oldPage); + throw e; + } + } + public void refresh() throws SQLException { this.loadData(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java index 36f69ac..6f87392 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/DataViewData.java @@ -10,6 +10,7 @@ @Data public class DataViewData { + private boolean editable; private List> data = new ArrayList<>(); private List fields = new ArrayList<>(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/QueryDataViewTableEditContextFactory.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/QueryDataViewTableEditContextFactory.java new file mode 100644 index 0000000..c874f05 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/QueryDataViewTableEditContextFactory.java @@ -0,0 +1,148 @@ +package org.jumpserver.chen.framework.console.dataview; + +import com.alibaba.druid.DbType; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.edit.TableEditContext; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.List; +import java.util.Objects; + +public class QueryDataViewTableEditContextFactory { + public static final String FIELDS_REQUIRED = "FIELDS_REQUIRED"; + public static final String PRIMARY_KEY_REQUIRED = "PRIMARY_KEY_REQUIRED"; + public static final String PRIMARY_KEY_SOURCE_COLUMN_REQUIRED = "PRIMARY_KEY_SOURCE_COLUMN_REQUIRED"; + public static final String EDITABLE_FIELD_REQUIRED = "EDITABLE_FIELD_REQUIRED"; + public static final String EDITABLE_FIELD_SOURCE_REQUIRED = "EDITABLE_FIELD_SOURCE_REQUIRED"; + public static final String MIXED_SOURCE_TABLE_NOT_SUPPORTED = "MIXED_SOURCE_TABLE_NOT_SUPPORTED"; + public static final String MIXED_SOURCE_SCHEMA_NOT_SUPPORTED = "MIXED_SOURCE_SCHEMA_NOT_SUPPORTED"; + public static final String SOURCE_TABLE_REQUIRED = "SOURCE_TABLE_REQUIRED"; + + public TableEditContext create(DataView dataView, DbType dbType) { + List fields = getRequiredFields(dataView); + + Field primaryKey = getRequiredPrimaryKey(fields); + validatePrimaryKey(primaryKey); + + List editableFields = getRequiredEditableFields(fields); + validateEditableFields(editableFields); + + ensureSingleSource(fields); + + String sourceTable = resolveSourceTable(primaryKey, editableFields); + String sourceSchema = resolveSourceSchema(primaryKey); + + ensureEditableFieldsMatchSource(editableFields, sourceSchema, sourceTable); + + return new TableEditContext(dataView.getTitle(), sourceSchema, sourceTable, fields, dbType); + } + + private List getRequiredFields(DataView dataView) { + List fields = dataView.getData().getFields(); + if (fields == null || fields.isEmpty()) { + throw new IllegalArgumentException(FIELDS_REQUIRED); + } + return fields; + } + + private Field getRequiredPrimaryKey(List fields) { + return fields.stream() + .filter(Objects::nonNull) + .filter(Field::isPrimaryKey) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(PRIMARY_KEY_REQUIRED)); + } + + private void validatePrimaryKey(Field primaryKey) { + if (StringUtils.isBlank(primaryKey.getSourceColumn())) { + throw new IllegalArgumentException(PRIMARY_KEY_SOURCE_COLUMN_REQUIRED); + } + } + + private List getRequiredEditableFields(List fields) { + List editableFields = fields.stream() + .filter(Objects::nonNull) + .filter(Field::isEditable) + .toList(); + + if (editableFields.isEmpty()) { + throw new IllegalArgumentException(EDITABLE_FIELD_REQUIRED); + } + + return editableFields; + } + + private void validateEditableFields(List editableFields) { + for (Field field : editableFields) { + if (StringUtils.isBlank(field.getSourceTable()) || StringUtils.isBlank(field.getSourceColumn())) { + throw new IllegalArgumentException(EDITABLE_FIELD_SOURCE_REQUIRED); + } + } + } + + private void ensureSingleSource(List fields) { + ensureSingleSourceTable(fields); + ensureSingleSourceSchema(fields); + } + + private void ensureSingleSourceTable(List fields) { + long sourceTableCount = fields.stream() + .filter(Objects::nonNull) + .map(Field::getSourceTable) + .filter(StringUtils::isNotBlank) + .distinct() + .count(); + + if (sourceTableCount > 1) { + throw new IllegalArgumentException(MIXED_SOURCE_TABLE_NOT_SUPPORTED); + } + } + + private void ensureSingleSourceSchema(List fields) { + long sourceSchemaCount = fields.stream() + .filter(Objects::nonNull) + .map(Field::getSourceSchema) + .filter(StringUtils::isNotBlank) + .distinct() + .count(); + + if (sourceSchemaCount > 1) { + throw new IllegalArgumentException(MIXED_SOURCE_SCHEMA_NOT_SUPPORTED); + } + } + + private String resolveSourceTable(Field primaryKey, List editableFields) { + String sourceTable = primaryKey.getSourceTable(); + + if (StringUtils.isBlank(sourceTable)) { + sourceTable = editableFields.get(0).getSourceTable(); + } + + if (StringUtils.isBlank(sourceTable)) { + throw new IllegalArgumentException(SOURCE_TABLE_REQUIRED); + } + + return sourceTable; + } + + private String resolveSourceSchema(Field primaryKey) { + // 保持原逻辑:sourceSchema 以主键字段为准,不从 editableFields fallback。 + return primaryKey.getSourceSchema(); + } + + private void ensureEditableFieldsMatchSource( + List editableFields, + String sourceSchema, + String sourceTable + ) { + for (Field editableField : editableFields) { + if (!StringUtils.equals(sourceTable, editableField.getSourceTable())) { + throw new IllegalArgumentException(MIXED_SOURCE_TABLE_NOT_SUPPORTED); + } + + if (!StringUtils.equals(sourceSchema, editableField.getSourceSchema())) { + throw new IllegalArgumentException(MIXED_SOURCE_SCHEMA_NOT_SUPPORTED); + } + } + } +} \ No newline at end of file diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java index b390f4d..e5f7559 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/dataview/UpdateDataView.java @@ -4,10 +4,16 @@ @Data public class UpdateDataView { + private String id; private String title; private DataViewData data; public UpdateDataView(String title, DataViewData data) { + this(title, title, data); + } + + public UpdateDataView(String id, String title, DataViewData data) { + this.id = id; this.title = title; this.data = data; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java index f339325..acced2d 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/Connect.java @@ -6,6 +6,7 @@ public class Connect { public static final String CONSOLE_TYPE_QUERY = "query"; + public static final String CONSOLE_TYPE_CONSOLE = "console"; public static final String CONSOLE_TYPE_DATA_VIEW = "data_view"; private String nodeKey; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/SaveChangesRequest.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/SaveChangesRequest.java new file mode 100644 index 0000000..5b39fea --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/request/SaveChangesRequest.java @@ -0,0 +1,45 @@ +package org.jumpserver.chen.framework.console.entity.request; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class SaveChangesRequest { + private String schema; + private String table; + private List changes; + private List insertRows; + private List deleteRows; + + @Data + public static class ChangeItem { + private String pkColumn; + private Object pkValue; + private boolean pkValueIsNull; + private String sourceColumn; + private Object oldValue; + private boolean oldValueIsNull; + private Object newValue; + private boolean newValueIsNull; + } + + @Data + public static class InsertRow { + private Map values; + } + + @Data + public static class DeleteRow { + private String pkColumn; + private Object pkValue; + private boolean pkValueIsNull; + } + + @Data + public static class CellValue { + private Object value; + private boolean valueIsNull; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesPreviewResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesPreviewResult.java new file mode 100644 index 0000000..4be0c96 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesPreviewResult.java @@ -0,0 +1,35 @@ +package org.jumpserver.chen.framework.console.entity.response; + +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SaveChangesPreviewResult { + private boolean success; + private boolean allowed; + private String reason; + private Integer failedChangeIndex; + private SaveChangesRequest.ChangeItem failedChange; + private Object failedOperation; + private String dataView; + private String schema; + private String table; + private int changeCount; + private int updateCount; + private int insertCount; + private int deleteCount; + private List preparedStatements = new ArrayList<>(); + private String auditSql; + + @Data + public static class PreviewItem { + private String operation; + private String sourceColumn; + private String pkColumn; + private String preparedSql; + private List paramsPreview; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesResult.java new file mode 100644 index 0000000..2c6d6b4 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/entity/response/SaveChangesResult.java @@ -0,0 +1,39 @@ +package org.jumpserver.chen.framework.console.entity.response; + +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SaveChangesResult { + private boolean success; + private boolean allowed; + private boolean databaseChangesApplied; + private boolean databaseCommitted; + private boolean auditSucceeded; + private boolean connectionInvalidated; + private String reason; + private Integer failedChangeIndex; + private SaveChangesRequest.ChangeItem failedChange; + private Object failedOperation; + private String dataView; + private String schema; + private String table; + private int changeCount; + private int updateCount; + private int insertCount; + private int deleteCount; + private List statements = new ArrayList<>(); + private String auditSql; + + @Data + public static class ResultItem { + private String operation; + private String sourceColumn; + private String pkColumn; + private String preparedSql; + private int affectedRows; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java index 4f75ac6..59f30b4 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/DataViewState.java @@ -8,19 +8,25 @@ @EqualsAndHashCode(callSuper = true) @Data public class DataViewState extends State { + private String id; private int page; private int limit; private int total; + private boolean truncated; + private int rowLimit; private boolean pinned; private boolean paged; + private String filter; - public DataViewState(String title) { + public DataViewState(String id, String title) { super(title); + this.id = id; this.paged = true; this.pinned = false; this.total = 0; this.page = 1; this.limit = 50; + this.filter = ""; } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java index 40a6679..bd1a779 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/state/QueryConsoleState.java @@ -9,12 +9,13 @@ @EqualsAndHashCode(callSuper = true) @Data public class QueryConsoleState extends State { - private String currentContext; + private volatile String currentContext; private boolean inQuery; private List contexts; private int timeout; private boolean editorLoading; private boolean canCancel; + private volatile String executionStatus; public QueryConsoleState(String title) { super(title); @@ -23,6 +24,7 @@ public QueryConsoleState(String title) { this.timeout = 30; this.editorLoading = false; this.canCancel = false; + this.executionStatus = ""; } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java new file mode 100644 index 0000000..b3ecac4 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/DamengTransactionStateProbe.java @@ -0,0 +1,65 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Locale; + +final class DamengTransactionStateProbe implements TransactionStateProbe { + private static final String DRIVER_CONNECTION = "dm.jdbc.driver.DmdbConnection"; + private static final int STATE_MASK = 0x0FFF; + private static final int NOT_STARTED = 0; + private static final int COMMITTED = 32; + private static final int ROLLED_BACK = 64; + private static final String STATE_SQL = + "SELECT STATUS FROM V$TRX WHERE SESS_ID = SESSID"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + Class driverClass = Class.forName( + DRIVER_CONNECTION, + false, + connection.getClass().getClassLoader() + ); + Object driverConnection = driverClass.isInstance(connection) + ? connection + : connection.unwrap(driverClass); + int transactionStatus = driverClass.getField("trxStatus").getInt(driverConnection); + QueryTransactionState driverState = map(transactionStatus, connection.getAutoCommit()); + if (driverState != QueryTransactionState.TRANSACTION_ACTIVE) { + return driverState; + } + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + return mapServerStatus(resultSet.getString(1)); + } + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | + SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + static QueryTransactionState map(int transactionStatus, boolean autoCommit) { + int state = transactionStatus & STATE_MASK; + if (state == NOT_STARTED || state == COMMITTED || state == ROLLED_BACK) { + return autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } + return QueryTransactionState.TRANSACTION_ACTIVE; + } + + static QueryTransactionState mapServerStatus(String status) { + if (status == null) { + return QueryTransactionState.UNKNOWN; + } + return switch (status.toUpperCase(Locale.ROOT).trim()) { + case "ACTIVE", "LOCK WAIT" -> QueryTransactionState.TRANSACTION_ACTIVE; + case "ROLLING", "ROLLING_SUSPEND" -> QueryTransactionState.TRANSACTION_FAILED; + default -> QueryTransactionState.UNKNOWN; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/Db2TransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/Db2TransactionStateProbe.java new file mode 100644 index 0000000..7c1fb24 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/Db2TransactionStateProbe.java @@ -0,0 +1,39 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.sql.Connection; +import java.sql.SQLException; + +final class Db2TransactionStateProbe implements TransactionStateProbe { + private static final String DRIVER_CONNECTION = "com.ibm.db2.jcc.DB2Connection"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + Class driverClass = Class.forName( + DRIVER_CONNECTION, + false, + connection.getClass().getClassLoader() + ); + Object driverConnection = driverClass.isInstance(connection) + ? connection + : connection.unwrap(driverClass); + boolean inUnitOfWork = (boolean) driverClass + .getMethod("isInDB2UnitOfWork") + .invoke(driverConnection); + return map(inUnitOfWork, connection.getAutoCommit()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + static QueryTransactionState map(boolean inUnitOfWork, boolean autoCommit) { + if (inUnitOfWork) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlDriverTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlDriverTransactionStateProbe.java new file mode 100644 index 0000000..6c971d8 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlDriverTransactionStateProbe.java @@ -0,0 +1,42 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.sql.Connection; +import java.sql.SQLException; + +final class MysqlDriverTransactionStateProbe implements TransactionStateProbe { + private static final String JDBC_CONNECTION = "com.mysql.cj.jdbc.JdbcConnection"; + private static final String SESSION = "com.mysql.cj.Session"; + private static final String SERVER_SESSION = "com.mysql.cj.protocol.ServerSession"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + ClassLoader classLoader = connection.getClass().getClassLoader(); + Class jdbcConnectionClass = Class.forName(JDBC_CONNECTION, false, classLoader); + Object jdbcConnection = jdbcConnectionClass.isInstance(connection) + ? connection + : connection.unwrap(jdbcConnectionClass); + Object session = jdbcConnectionClass.getMethod("getSession").invoke(jdbcConnection); + Class sessionClass = Class.forName(SESSION, false, classLoader); + Object serverSession = sessionClass.getMethod("getServerSession").invoke(session); + Class serverSessionClass = Class.forName(SERVER_SESSION, false, classLoader); + boolean inTransaction = (boolean) serverSessionClass + .getMethod("inTransactionOnServer") + .invoke(serverSession); + return map(inTransaction, connection.getAutoCommit()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + static QueryTransactionState map(boolean inTransaction, boolean autoCommit) { + if (inTransaction) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlTransactionStateProbe.java new file mode 100644 index 0000000..574d3ef --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/MysqlTransactionStateProbe.java @@ -0,0 +1,27 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; +import java.sql.SQLException; + +final class MysqlTransactionStateProbe implements TransactionStateProbe { + private static final String STATE_SQL = + "SELECT @@session.autocommit, @@session.in_transaction"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + if (resultSet.getBoolean(2)) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return resultSet.getBoolean(1) + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } catch (SQLException e) { + throw new TransactionStateProbeException(e); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/OracleTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/OracleTransactionStateProbe.java new file mode 100644 index 0000000..9bbc539 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/OracleTransactionStateProbe.java @@ -0,0 +1,28 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; +import java.sql.SQLException; + +final class OracleTransactionStateProbe implements TransactionStateProbe { + private static final String STATE_SQL = + "SELECT DBMS_TRANSACTION.LOCAL_TRANSACTION_ID(FALSE) FROM DUAL"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + String transactionId = resultSet.getString(1); + if (transactionId != null && !transactionId.isBlank()) { + return QueryTransactionState.TRANSACTION_ACTIVE; + } + return connection.getAutoCommit() + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } catch (SQLException e) { + throw new TransactionStateProbeException(e); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/PostgresqlTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/PostgresqlTransactionStateProbe.java new file mode 100644 index 0000000..74567ba --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/PostgresqlTransactionStateProbe.java @@ -0,0 +1,39 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.sql.Connection; +import java.sql.SQLException; + +final class PostgresqlTransactionStateProbe implements TransactionStateProbe { + private static final String BASE_CONNECTION = "org.postgresql.core.BaseConnection"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + ClassLoader classLoader = connection.getClass().getClassLoader(); + Class baseConnectionClass = Class.forName(BASE_CONNECTION, false, classLoader); + Object baseConnection = baseConnectionClass.isInstance(connection) + ? connection + : connection.unwrap(baseConnectionClass); + Object transactionState = baseConnectionClass + .getMethod("getTransactionState") + .invoke(baseConnection); + + return map(transactionState.toString(), connection.getAutoCommit()); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + static QueryTransactionState map(String driverState, boolean autoCommit) { + return switch (driverState) { + case "OPEN" -> QueryTransactionState.TRANSACTION_ACTIVE; + case "FAILED" -> QueryTransactionState.TRANSACTION_FAILED; + case "IDLE" -> autoCommit + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + default -> QueryTransactionState.UNKNOWN; + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionProbeResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionProbeResult.java new file mode 100644 index 0000000..b3220e5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionProbeResult.java @@ -0,0 +1,20 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.util.Objects; + +public record QueryTransactionProbeResult( + QueryTransactionState state, + boolean probeFailed +) { + public QueryTransactionProbeResult { + Objects.requireNonNull(state, "state"); + } + + static QueryTransactionProbeResult observed(QueryTransactionState state) { + return new QueryTransactionProbeResult(state, false); + } + + static QueryTransactionProbeResult failed() { + return new QueryTransactionProbeResult(QueryTransactionState.UNKNOWN, true); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionState.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionState.java new file mode 100644 index 0000000..bedf72d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionState.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.console.transaction; + +public enum QueryTransactionState { + AUTO_COMMIT, + MANUAL_COMMIT_IDLE, + TRANSACTION_ACTIVE, + TRANSACTION_FAILED, + UNKNOWN +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionStateInspector.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionStateInspector.java new file mode 100644 index 0000000..f3a2273 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/QueryTransactionStateInspector.java @@ -0,0 +1,43 @@ +package org.jumpserver.chen.framework.console.transaction; + +import com.alibaba.druid.DbType; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; + +@Slf4j +public final class QueryTransactionStateInspector { + private final Connection connection; + private final TransactionStateProbe probe; + + public static QueryTransactionStateInspector create(DbType dbType, Connection connection) { + TransactionStateProbe probe = switch (dbType) { + case postgresql -> new PostgresqlTransactionStateProbe(); + case mysql -> new MysqlDriverTransactionStateProbe(); + case mariadb -> new MysqlTransactionStateProbe(); + case oracle -> new OracleTransactionStateProbe(); + case sqlserver -> new SqlServerTransactionStateProbe(); + case dm -> new DamengTransactionStateProbe(); + case db2 -> new Db2TransactionStateProbe(); + default -> ignored -> QueryTransactionState.UNKNOWN; + }; + return new QueryTransactionStateInspector(connection, probe); + } + + QueryTransactionStateInspector(Connection connection, TransactionStateProbe probe) { + this.connection = connection; + this.probe = probe; + } + + public QueryTransactionProbeResult probeNow() { + try { + return QueryTransactionProbeResult.observed(this.probe.inspect(this.connection)); + } catch (TransactionStateProbeException e) { + // Only explicit probe failures (SQLException / driver-compat reflection errors wrapped + // by the probe) become a probeFailed result. Program errors such as NPE, + // ClassCastException or IllegalStateException are NOT caught here and must propagate. + log.debug("probe transaction state failed", e); + return QueryTransactionProbeResult.failed(); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/SqlServerTransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/SqlServerTransactionStateProbe.java new file mode 100644 index 0000000..11c0fed --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/SqlServerTransactionStateProbe.java @@ -0,0 +1,64 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.lang.reflect.InvocationTargetException; +import java.sql.Connection; +import java.sql.SQLException; + +final class SqlServerTransactionStateProbe implements TransactionStateProbe { + private static final String DRIVER_CONNECTION = "com.microsoft.sqlserver.jdbc.SQLServerConnection"; + private static final String STATE_SQL = "SELECT XACT_STATE()"; + + @Override + public QueryTransactionState inspect(Connection connection) { + try { + if (!hasTransactionDescriptor(connection)) { + return connection.getAutoCommit() + ? QueryTransactionState.AUTO_COMMIT + : QueryTransactionState.MANUAL_COMMIT_IDLE; + } + try (var statement = connection.createStatement(); + var resultSet = statement.executeQuery(STATE_SQL)) { + if (!resultSet.next()) { + return QueryTransactionState.UNKNOWN; + } + return mapXactState(resultSet.getInt(1)); + } + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | SQLException e) { + throw new TransactionStateProbeException(e); + } + } + + static QueryTransactionState mapXactState(int state) { + return switch (state) { + case 1 -> QueryTransactionState.TRANSACTION_ACTIVE; + case -1 -> QueryTransactionState.TRANSACTION_FAILED; + default -> QueryTransactionState.UNKNOWN; + }; + } + + private static boolean hasTransactionDescriptor(Connection connection) + throws ClassNotFoundException, SQLException, NoSuchMethodException, + InvocationTargetException, IllegalAccessException { + Class driverClass = Class.forName(DRIVER_CONNECTION, false, connection.getClass().getClassLoader()); + Object driverConnection = driverClass.isInstance(connection) + ? connection + : connection.unwrap(driverClass); + var method = driverClass.getDeclaredMethod("getTransactionDescriptor"); + method.setAccessible(true); + byte[] descriptor = (byte[]) method.invoke(driverConnection); + return hasTransactionDescriptor(descriptor); + } + + static boolean hasTransactionDescriptor(byte[] descriptor) { + if (descriptor == null) { + return false; + } + for (byte value : descriptor) { + if (value != 0) { + return true; + } + } + return false; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbe.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbe.java new file mode 100644 index 0000000..9e340d4 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbe.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.console.transaction; + +import java.sql.Connection; + +@FunctionalInterface +interface TransactionStateProbe { + QueryTransactionState inspect(Connection connection); +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbeException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbeException.java new file mode 100644 index 0000000..ae4a986 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/console/transaction/TransactionStateProbeException.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.console.transaction; + +final class TransactionStateProbeException extends RuntimeException { + TransactionStateProbeException(Throwable cause) { + super(cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java index 1978374..3d0d94b 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ConnectionManager.java @@ -20,6 +20,8 @@ public interface ConnectionManager { void setDatabaseContext(String database); + T withDatabaseContext(String database, DatabaseContextAction action) throws SQLException; + Datasource getDatasource(); DBConnectInfo getConnectInfo(); @@ -30,7 +32,16 @@ public interface ConnectionManager { SQLActuator getSqlActuator(); + // 对象树/编辑器当前上下文使用的 key,例如 schema。 String getContextKey(); + // JDBC URL/连接池默认库使用的 key,不能和对象树上下文混用。 + String getDatabaseContextKey(); + void close(); + + @FunctionalInterface + interface DatabaseContextAction { + T run() throws SQLException; + } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java index c0bc523..7817462 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/Datasource.java @@ -8,6 +8,7 @@ import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.datasource.entity.action.Action; import org.jumpserver.chen.framework.datasource.entity.form.FormData; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; import java.sql.SQLException; @@ -35,6 +36,7 @@ public interface Datasource { ConnectionManager getConnectionManager(); ResourceBrowser getResourceBrowser(); + MetadataCatalog getMetadataCatalog(); void close(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java index e3e512f..5082cd7 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/DatasourceFactory.java @@ -3,6 +3,7 @@ import lombok.extern.slf4j.Slf4j; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -11,10 +12,26 @@ public class DatasourceFactory { private static final Map> DATASOURCE_MAP = new ConcurrentHashMap<>(); + private static final Map DATASOURCE_CLASS_NAMES = new HashMap<>(); + + static { + DATASOURCE_CLASS_NAMES.put("clickhouse", "org.jumpserver.chen.modules.clickhouse.ClickHouseDatasource"); + DATASOURCE_CLASS_NAMES.put("postgresql", "org.jumpserver.chen.modules.postgresql.PostgresqlDatasource"); + DATASOURCE_CLASS_NAMES.put("mariadb", "org.jumpserver.chen.modules.mariadb.MariaDBDatasource"); + DATASOURCE_CLASS_NAMES.put("oracle", "org.jumpserver.chen.modules.oracle.OracleDatasource"); + DATASOURCE_CLASS_NAMES.put("dameng", "org.jumpserver.chen.modules.dameng.DMDatasource"); + DATASOURCE_CLASS_NAMES.put("mysql", "org.jumpserver.chen.modules.mysql.MysqlDatasource"); + DATASOURCE_CLASS_NAMES.put("sqlserver", "org.jumpserver.chen.modules.sqlserver.SQLServerDatasource"); + DATASOURCE_CLASS_NAMES.put("db2", "org.jumpserver.chen.modules.db2.DB2Datasource"); + } public static Datasource fromConnectInfo(DBConnectInfo info) { var dbType = info.getDbType(); - var datasource = DATASOURCE_MAP.get(info.getDbType()); + var datasource = DATASOURCE_MAP.get(dbType); + if (datasource == null) { + lazyRegister(dbType); + datasource = DATASOURCE_MAP.get(dbType); + } if (datasource == null) { throw new RuntimeException("Unsupported dbType: " + dbType); } @@ -31,4 +48,18 @@ public static void Register(Class datasource) { log.info("Register datasource for dbType: {}", datasource.getName()); DATASOURCE_MAP.put(dbType, datasource); } + + @SuppressWarnings("unchecked") + private static void lazyRegister(String dbType) { + var className = DATASOURCE_CLASS_NAMES.get(dbType); + if (className == null) { + return; + } + try { + var datasourceClass = (Class) Class.forName(className); + Register(datasourceClass); + } catch (ClassNotFoundException e) { + log.warn("Datasource class not found for dbType {}: {}", dbType, className); + } + } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java index 6e2c05d..f45e2d3 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/ResourceBrowser.java @@ -1,8 +1,7 @@ package org.jumpserver.chen.framework.datasource; import org.jumpserver.chen.framework.datasource.entity.resource.*; -import org.jumpserver.chen.framework.datasource.hints.SQLHintsHandler; -import org.jumpserver.chen.framework.datasource.sql.SQL; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; import org.jumpserver.chen.framework.datasource.sql.SQLActuator; import java.sql.SQLException; @@ -17,14 +16,17 @@ public interface ResourceBrowser { List getChildren(TreeNode node, boolean fromCache) throws SQLException; - List getSchemas(SQL sql) throws SQLException; + List getSchemas() throws SQLException; - List getTables(SQL sql) throws SQLException; + List
getTables(String schema) throws SQLException; - List getViews(SQL sql) throws SQLException; + List getViews(String schema) throws SQLException; - List getFields(SQL sql) throws SQLException; + List getFields(String schema, String table) throws SQLException; + + RelationScope resolveScope(ResourceNodeSnapshot node, String context) throws SQLException; + + ResourceNodeSnapshot getIndexedNode(String key); SQLActuator getSQLActuator(); - SQLHintsHandler getSQLHintsHandler(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java index f2fa7bd..ff2d893 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseActionHandler.java @@ -8,6 +8,8 @@ import org.jumpserver.chen.framework.datasource.entity.dialog.detail.DetailDialog; import org.jumpserver.chen.framework.datasource.entity.dialog.detail.DetailItem; import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.session.SessionManager; @@ -180,6 +182,35 @@ public EventEmitter onShowObjectProperties(String type, String sql, TreeNode nod return EventEmitter.of("new_dialog", detailDialog); } + public EventEmitter onTableProperties(TreeNode node) throws SQLException { + return this.showRelationProperties(node); + } + + public EventEmitter onViewProperties(TreeNode node) throws SQLException { + return this.showRelationProperties(node); + } + + private EventEmitter showRelationProperties(TreeNode node) throws SQLException { + var database = TreeUtils.getValue(node.getKey(), "database"); + var schema = TreeUtils.getValue(node.getKey(), "schema"); + var table = TreeUtils.getValue(node.getKey(), "table"); + var kind = "view".equals(node.getType()) ? RelationKind.VIEW : RelationKind.TABLE; + var ref = new ObjectRef(database.isEmpty() ? null : database, schema, table, kind); + var properties = this.getDatasource().getMetadataCatalog().objectProperties(ref); + + var detailDialog = new DetailDialog(node.getKey(), node.getType() + MessageUtils.get("Properties")); + detailDialog.setWidth("50%"); + for (var item : properties.items()) { + detailDialog.addItem(DetailItem.builder() + .name(item.name()) + .label(item.name()) + .type("text") + .value(item.value() == null ? "" : item.value()) + .build()); + } + return EventEmitter.of("new_dialog", detailDialog); + } + private Object runTargetMethod(String methodName, TreeNode node) throws ReflectiveOperationException { return this.getClass() .getMethod(methodName, TreeNode.class) diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java index b3d1dfc..96c80f4 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseConnectionManager.java @@ -76,7 +76,26 @@ public Driver getDriver() { @Override public void setDatabaseContext(String database) { - this.currentDatabase.set(database); + if (StringUtils.isBlank(database)) { + this.currentDatabase.remove(); + } else { + this.currentDatabase.set(database); + } + } + + @Override + public T withDatabaseContext(String database, DatabaseContextAction action) throws SQLException { + var previousDatabase = this.currentDatabase.get(); + try { + this.setDatabaseContext(database); + return action.run(); + } finally { + if (previousDatabase == null) { + this.currentDatabase.remove(); + } else { + this.currentDatabase.set(previousDatabase); + } + } } @Override @@ -84,6 +103,12 @@ public String getContextKey() { return "schema"; } + @Override + public String getDatabaseContextKey() { + // 大多数数据库的连接上下文是 database;MySQL/MariaDB 会覆盖为 schema。 + return "database"; + } + @Override public DBConnectInfo getConnectInfo() { return this.connectInfo; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java index 21b01d3..5529deb 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseDatasource.java @@ -10,6 +10,7 @@ import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.datasource.entity.action.Action; import org.jumpserver.chen.framework.datasource.entity.form.FormData; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; import org.jumpserver.chen.framework.session.SessionManager; import java.sql.SQLException; @@ -22,6 +23,7 @@ public abstract class BaseDatasource implements Datasource { protected ResourceBrowser resourceBrowser; protected ActionHandler actionHandler; protected DatasourceInfo datasourceInfo; + protected MetadataCatalog metadataCatalog; public void ping() { try { @@ -84,6 +86,10 @@ public ResourceBrowser getResourceBrowser() { return this.resourceBrowser; } + public MetadataCatalog getMetadataCatalog() { + return this.metadataCatalog; + } + public DBConnectInfo getConnectInfo() { return this.connectionManager.getConnectInfo(); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java index 7bc4832..f12e9e7 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseResourceBrowser.java @@ -2,21 +2,26 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.ResourceBrowser; -import org.jumpserver.chen.framework.datasource.hints.SQLHintsHandler; -import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.datasource.entity.resource.*; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; import org.jumpserver.chen.framework.datasource.sql.SQLActuator; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.utils.SqlIdentifierUtils; import org.jumpserver.chen.framework.utils.TreeUtils; import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; @Slf4j @@ -26,17 +31,10 @@ public abstract class BaseResourceBrowser implements ResourceBrowser { @Getter private final ConnectionManager connectionManager; - private final SQLHintsHandler sqlHintsHandler; + private final ConcurrentHashMap nodeIndex = new ConcurrentHashMap<>(); - - @Override - public SQLHintsHandler getSQLHintsHandler() { - return this.sqlHintsHandler; - } - - public BaseResourceBrowser(ConnectionManager connectionManager, SQLHintsHandler sqlHintsHandler) { + public BaseResourceBrowser(ConnectionManager connectionManager) { this.connectionManager = connectionManager; - this.sqlHintsHandler = sqlHintsHandler; } @Override @@ -44,7 +42,7 @@ public void buildTree() throws SQLException { var root = new Root(); root.setName(SessionManager.getCurrentSession().getDatasourceName()); - this.root = root.toResourceNode(null); + this.resetNodeIndex(root.toResourceNode(null)); var parents = List.of(this.root); while (!parents.isEmpty()) { @@ -86,8 +84,9 @@ public List getChildren(TreeNode node, boolean fromCache) throws SQLEx return n.getChildren(); } } + var cachedNode = TreeUtils.getNode(this.root, node.getKey()); var children = this.getChildNodes(node); - if (!children.isEmpty()) { + if (!children.isEmpty() || cachedNode != null && cachedNode.getChildren() != null) { this.saveTreeNode(node, children); } return children; @@ -129,72 +128,179 @@ public List getSchemaChildNodes(TreeNode parent) throws SQLException { public List getFolderChildNodes(TreeNode parent) throws SQLException { String folder = TreeUtils.getValue(parent.getKey(), "folder"); String schema = TreeUtils.getValue(parent.getKey(), "schema"); + String database = TreeUtils.getValue(parent.getKey(), "database"); + var scope = new RelationScope(database.isEmpty() ? null : database, schema); return switch (Objects.requireNonNull(folder)) { - case "tables" -> this.getTables(schema) + case "tables" -> this.metadataCatalog().listRelations(scope, Set.of(RelationKind.TABLE)) .stream() - .map(table -> table.toResourceNode(parent)) + .map(relation -> { + var table = new Table(); + table.setName(relation.ref().name()); + table.setSchema(relation.ref().schema()); + return table.toResourceNode(parent); + }) .toList(); - case "views" -> this.getViews(schema) + case "views" -> this.metadataCatalog().listRelations(scope, Set.of(RelationKind.VIEW)) .stream() - .map(view -> view.toResourceNode(parent)) + .map(relation -> { + var view = new View(); + view.setName(relation.ref().name()); + view.setSchema(relation.ref().schema()); + return view.toResourceNode(parent); + }) .toList(); default -> List.of(); }; } - public void saveTreeNode(TreeNode node, List children) { + public synchronized void saveTreeNode(TreeNode node, List children) { var n = TreeUtils.getNode(this.root, node.getKey()); if (n != null) { n.setChildren(children); + this.removeIndexedDescendants(n.getKey()); + var parent = this.nodeIndex.get(n.getKey()); + for (var child : children) { + this.registerNode(child, parent); + } } } - public abstract List getSchemas() throws SQLException; + protected synchronized void resetNodeIndex(TreeNode root) { + this.root = root; + this.nodeIndex.clear(); + this.registerNode(root, null); + } @Override - public List getSchemas(SQL sql) throws SQLException { - var currentSchema = ""; - List schemas = new ArrayList<>(); - schemas.addAll(this.getSQLActuator().getObjects(sql.getSql(), Schema.class, Map.of("name", 1))); - schemas.sort((o1, o2) -> { - if (o1.getName().equalsIgnoreCase(currentSchema)) { - return -1; - } else if (o2.getName().equalsIgnoreCase(currentSchema)) { - return 1; - } else { - return 0; - } - }); - return schemas; + public synchronized ResourceNodeSnapshot getIndexedNode(String key) { + return key == null ? null : this.nodeIndex.get(key); } - public abstract List
getTables(String schema) throws SQLException; + private void registerNode(TreeNode node, ResourceNodeSnapshot parent) { + String database = parent == null ? null : parent.database(); + String schema = parent == null ? null : parent.schema(); + String table = parent == null ? null : parent.table(); - @Override - public List
getTables(SQL sql) throws SQLException { - return new ArrayList<>(this.getSQLActuator().getObjects(sql.getSql(), Table.class, Map.of("name", 1))); + switch (node.getType()) { + case "database" -> { + database = node.getLabel(); + schema = null; + table = null; + } + case "schema" -> { + schema = node.getLabel(); + if (this.connectionManager != null && + Objects.equals(this.connectionManager.getDatabaseContextKey(), "schema")) { + database = node.getLabel(); + } + table = null; + } + case "table", "view" -> table = node.getLabel(); + default -> { + } + } + + this.nodeIndex.put(node.getKey(), new ResourceNodeSnapshot( + node.getKey(), + node.getType(), + database, + schema, + table, + node.getLabel() + )); + if (node.getChildren() != null) { + var snapshot = this.nodeIndex.get(node.getKey()); + for (var child : node.getChildren()) { + this.registerNode(child, snapshot); + } + } } - public abstract List getViews(String schema) throws SQLException; + private void removeIndexedDescendants(String parentKey) { + String prefix = parentKey + ","; + this.nodeIndex.keySet().removeIf(key -> key.startsWith(prefix)); + } - @Override - public List getViews(SQL sql) throws SQLException { - return new ArrayList<>(this.getSQLActuator().getObjects(sql.getSql(), View.class, Map.of("name", 1))); + public List getSchemas() throws SQLException { + return this.metadataCatalog().listSchemas(null).stream() + .map(schema -> { + var entity = new Schema(); + entity.setName(schema.name()); + return entity; + }) + .toList(); } - public abstract List getFields(String schema, String table) throws SQLException; + public List
getTables(String schema) throws SQLException { + return this.metadataCatalog().listRelations(new RelationScope(null, schema), Set.of(RelationKind.TABLE)).stream() + .map(relation -> { + var table = new Table(); + table.setName(relation.ref().name()); + table.setSchema(schema); + return table; + }) + .toList(); + } - @Override - public List getFields(SQL sql) throws SQLException { - Map fieldMapping = Map.of( - "name", 1, - "type", 2, - "nullable", 3); - return new ArrayList<>(this.getSQLActuator().getObjects(sql.getSql(), Field.class, fieldMapping)); + public List getViews(String schema) throws SQLException { + return this.metadataCatalog().listRelations(new RelationScope(null, schema), Set.of(RelationKind.VIEW)).stream() + .map(relation -> { + var view = new View(); + view.setName(relation.ref().name()); + view.setSchema(schema); + return view; + }) + .toList(); + } + + public List getFields(String schema, String table) throws SQLException { + var ref = new ObjectRef(null, schema, table, RelationKind.TABLE); + return this.metadataCatalog().listColumns(List.of(ref)).stream() + .map(column -> { + var field = new Field(); + field.setName(column.name()); + field.setType(column.nativeType()); + field.setNullable(column.nullable()); + return field; + }) + .toList(); } public SQLActuator getSQLActuator() { return this.connectionManager.getSqlActuator(); } + @Override + public RelationScope resolveScope(ResourceNodeSnapshot node, String context) throws SQLException { + if (node == null) { + throw new IllegalArgumentException("Invalid metadata context"); + } + var contextKey = this.connectionManager.getContextKey(); + var databaseContextKey = this.connectionManager.getDatabaseContextKey(); + var currentContext = StringUtils.defaultString(context).trim(); + var catalog = node.database(); + if (StringUtils.equals(contextKey, databaseContextKey) && StringUtils.isNotBlank(currentContext)) { + var allowedContexts = this.connectionManager.getSqlActuator().getSchemas(); + if (!allowedContexts.contains(currentContext)) { + throw new IllegalArgumentException("Unknown database metadata context"); + } + catalog = currentContext; + } + SqlIdentifierUtils.validateDatabaseName(catalog); + String schema = null; + if (StringUtils.equals(contextKey, "schema")) { + schema = StringUtils.defaultIfBlank(currentContext, node.schema()); + if (StringUtils.isNotBlank(catalog) && schema.startsWith(catalog + ".")) { + schema = schema.substring(catalog.length() + 1); + } + } else if (StringUtils.equals(node.database(), catalog)) { + schema = node.schema(); + } + return new RelationScope(catalog, schema); + } + + protected MetadataCatalog metadataCatalog() { + return this.connectionManager.getDatasource().getMetadataCatalog(); + } + } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java index 757aa54..d7d1577 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLActuator.java @@ -13,6 +13,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.entity.resource.Field; import org.jumpserver.chen.framework.datasource.sql.*; +import org.jumpserver.chen.framework.datasource.edit.analyzer.EditabilityReason; +import org.jumpserver.chen.framework.datasource.edit.pk.JdbcPrimaryKeyResolver; +import org.jumpserver.chen.framework.datasource.edit.analyzer.QueryResultEditabilityAnalyzer; import org.jumpserver.chen.framework.jms.exception.CommandRejectException; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.utils.HexUtils; @@ -25,9 +28,12 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.sql.*; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.StringJoiner; import java.util.regex.Pattern; @@ -40,6 +46,7 @@ public abstract class BaseSQLActuator implements SQLActuator { private final DbType druidDbType; // Keep large JDBC text values bounded so one cell cannot fail or stall the whole result view. private static final int MAX_TEXT_DISPLAY_LENGTH = 1024 * 1024; + private static final int RAW_RESULT_ROW_LIMIT = 1000; private static final String TRUNCATED_SUFFIX = "...[truncated]"; private ConnectionManager connectionManager; private Connection connection; @@ -120,6 +127,33 @@ public List getObjects(String sql, Class clazz, Map f return objects; } + @Override + public List> queryRows(String sql, List parameters) throws SQLException { + var rows = new ArrayList>(); + try (Connection conn = this.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + for (int index = 0; index < parameters.size(); index++) { + stmt.setObject(index + 1, parameters.get(index)); + } + try (ResultSet rs = stmt.executeQuery()) { + var metadata = rs.getMetaData(); + while (rs.next()) { + var row = new LinkedHashMap(); + for (int index = 1; index <= metadata.getColumnCount(); index++) { + row.put( + metadata.getColumnLabel(index).toLowerCase(Locale.ROOT), + this.normalizeJdbcValue(rs.getObject(index)) + ); + } + rows.add(row); + } + } + } catch (SQLException e) { + throw new SQLException("run metadata sql error, %s".formatted(e.getMessage()), e); + } + return rows; + } + @Override public SQLQueryResult execute(SQL sql) throws SQLException { var plan = this.createPlan(sql); @@ -129,12 +163,32 @@ public SQLQueryResult execute(SQL sql) throws SQLException { @Override public SQLQueryResult execute(SQLExecutePlan plan) throws SQLException { + return this.execute(plan, true); + } + + @Override + public SQLQueryResult executeRaw(SQLExecutePlan plan) throws SQLException { + SQLQueryResult executionResult = new SQLQueryResult(plan.getTargetSQL()); + executionResult.setAclResult(plan.getAclResult()); + executionResult.setHasResultSet(false); + try { + Statement statement = plan.createStatement(); + this.executeRawStatement(plan, statement, executionResult); + } finally { + if (plan.getConnection() instanceof DruidPooledConnection) { + plan.getConnection().close(); + } + } + return executionResult; + } + + private SQLQueryResult execute(SQLExecutePlan plan, boolean enrichResult) throws SQLException { String sql = plan.getTargetSQL(); SQLQueryResult result = new SQLQueryResult(sql); result.setAclResult(plan.getAclResult()); try { Statement statement = plan.createStatement(); - this.executeStatement(plan, statement, result); + this.executeStatement(plan, statement, result, enrichResult); } finally { if (plan.getConnection() instanceof DruidPooledConnection) { plan.getConnection().close(); @@ -143,7 +197,12 @@ public SQLQueryResult execute(SQLExecutePlan plan) throws SQLException { return result; } - private void executeStatement(SQLExecutePlan plan, Statement statement, SQLQueryResult result) throws SQLException { + private void executeStatement( + SQLExecutePlan plan, + Statement statement, + SQLQueryResult result, + boolean enrichResult + ) throws SQLException { try (statement) { result.setStartTime(new Time(System.currentTimeMillis())); @@ -153,65 +212,213 @@ private void executeStatement(SQLExecutePlan plan, Statement statement, SQLQuery result.setQueryFinishedTime(new Time(System.currentTimeMillis())); if (hasResult) { - var resultSet = statement.getResultSet(); - var metaData = resultSet.getMetaData(); - var columnCount = metaData.getColumnCount(); - - for (int i = 1; i <= columnCount; i++) { - Field field = new Field(); - - var fieldName = StringUtils.isNotEmpty(metaData.getColumnLabel(i)) ? - metaData.getColumnLabel(i) : metaData.getColumnName(i); - field.setName(fieldName); - field.setColumnName(metaData.getColumnName(i)); - field.setLabel(metaData.getColumnLabel(i)); - result.getFields().add(field); + try (ResultSet resultSet = statement.getResultSet()) { + this.readResultSet(resultSet, result, Integer.MAX_VALUE); } - - while (resultSet.next()) { - List fs = new ArrayList<>(); - for (int i = 1; i <= columnCount; i++) { - try { - fs.add(this.normalizeJdbcValue(resultSet.getObject(i))); - } catch (NoClassDefFoundError e) { - log.error(e.getMessage()); - } + result.setFetchFinishedTime(new Time(System.currentTimeMillis())); + if (enrichResult) { + markGeneratedColumns(plan.getConnection(), this.getDruidDbType(), result.getFields()); + this.analyzeResultEditability(plan, result); + } + this.handleDataMasking(result); + if (enrichResult) { + var total = this.count(plan); + if (total < 0) { + result.setTotal(result.getData().size()); + } else { + result.setPaged(true); + result.setTotal(total); } - result.getData().add(fs); + } else { + result.setTotal(result.getData().size()); } - resultSet.close(); - result.setFetchFinishedTime(new Time(System.currentTimeMillis())); + } else { + result.setUpdateCount(statement.getUpdateCount()); + } + result.setEndTime(new Time(System.currentTimeMillis())); + } catch (SQLException e) { + throw e; + } catch (Exception e) { + throw new SQLException(e.getMessage(), e); + } + } - // 数据脱敏 - this.handleDataMasking(result); + private void executeRawStatement( + SQLExecutePlan plan, + Statement statement, + SQLQueryResult executionResult + ) throws SQLException { + long executionStartedAt = System.currentTimeMillis(); + executionResult.setStartTime(new Time(executionStartedAt)); + + try (statement) { + try { + // Ask the driver for one sentinel row so truncation can be detected without rewriting SQL. + statement.setMaxRows(RAW_RESULT_ROW_LIMIT + 1); + } catch (SQLException e) { + log.debug("JDBC driver does not support Statement.setMaxRows", e); + } - var total = this.count(plan); - if (total < 0) { + long resultStartedAt = executionStartedAt; + boolean hasResultSet = statement.execute(plan.getTargetSQL()); + while (true) { + long queryFinishedAt = System.currentTimeMillis(); + if (hasResultSet) { + SQLQueryResult result = newRawResult(plan, resultStartedAt, queryFinishedAt, true); + try (ResultSet resultSet = statement.getResultSet()) { + result.setTruncated(this.readResultSet(resultSet, result, RAW_RESULT_ROW_LIMIT)); + } + result.setRowLimit(RAW_RESULT_ROW_LIMIT); result.setTotal(result.getData().size()); + result.setFetchFinishedTime(new Time(System.currentTimeMillis())); + result.setEndTime(result.getFetchFinishedTime()); + this.handleDataMasking(result); + executionResult.getResults().add(result); } else { - result.setPaged(true); - result.setTotal(total); + int updateCount = statement.getUpdateCount(); + if (updateCount == -1) { + break; + } + SQLQueryResult result = newRawResult(plan, resultStartedAt, queryFinishedAt, false); + result.setUpdateCount(updateCount); + result.setEndTime(new Time(System.currentTimeMillis())); + executionResult.getResults().add(result); } - } else { - result.setUpdateCount(statement.getUpdateCount()); + resultStartedAt = System.currentTimeMillis(); + hasResultSet = statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT); } - result.setEndTime(new Time(System.currentTimeMillis())); + + Time finishedAt = new Time(System.currentTimeMillis()); + executionResult.setQueryFinishedTime(finishedAt); + executionResult.setFetchFinishedTime(finishedAt); + executionResult.setEndTime(finishedAt); + } catch (SQLException e) { + throw e; } catch (Exception e) { - throw new SQLException(e.getMessage()); + throw new SQLException(e.getMessage(), e); } } + private SQLQueryResult newRawResult( + SQLExecutePlan plan, + long startedAt, + long queryFinishedAt, + boolean hasResultSet + ) { + SQLQueryResult result = new SQLQueryResult(plan.getTargetSQL()); + result.setAclResult(plan.getAclResult()); + result.setHasResultSet(hasResultSet); + result.setStartTime(new Time(startedAt)); + result.setQueryFinishedTime(new Time(queryFinishedAt)); + return result; + } + + private boolean readResultSet(ResultSet resultSet, SQLQueryResult result, int rowLimit) throws SQLException { + var metaData = resultSet.getMetaData(); + var columnCount = metaData.getColumnCount(); + for (int index = 1; index <= columnCount; index++) { + result.getFields().add(buildField(metaData, index)); + } + while (resultSet.next()) { + if (result.getData().size() >= rowLimit) { + return true; + } + List row = new ArrayList<>(); + for (int index = 1; index <= columnCount; index++) { + try { + row.add(this.normalizeJdbcValue(resultSet.getObject(index))); + } catch (NoClassDefFoundError e) { + log.error(e.getMessage()); + } + } + result.getData().add(row); + } + return false; + } + + private void analyzeResultEditability(SQLExecutePlan plan, SQLQueryResult result) { + var analyzer = new QueryResultEditabilityAnalyzer( + this.getDruidDbType(), + new JdbcPrimaryKeyResolver(plan.getConnection(), this.getDruidDbType()) + ); + analyzer.analyze(plan.getSourceSQL(), result.getFields()); + } + + static Field buildField(ResultSetMetaData metaData, int columnIndex) throws SQLException { + Field field = new Field(); + + String columnLabel = metaData.getColumnLabel(columnIndex); + String columnName = metaData.getColumnName(columnIndex); + String fieldName = StringUtils.isNotEmpty(columnLabel) ? columnLabel : columnName; + field.setName(fieldName); + field.setColumnName(columnName); + field.setLabel(columnLabel); + fillOptionalFieldMetadata(field, metaData, columnIndex); + return field; + } + + private static void fillOptionalFieldMetadata(Field field, ResultSetMetaData metaData, int columnIndex) { + //后续还要靠 SQL AST 和主键解析再判断 + field.setSchema(getNullableMetadataValue(() -> metaData.getSchemaName(columnIndex), "schema", columnIndex)); + field.setTable(getNullableMetadataValue(() -> metaData.getTableName(columnIndex), "table", columnIndex)); + field.setType(getNullableMetadataValue(() -> metaData.getColumnTypeName(columnIndex), "type", columnIndex)); + try { + field.setJdbcType(metaData.getColumnType(columnIndex)); + } catch (SQLException e) { + log.debug("read result set jdbc type metadata failed for column {}", columnIndex, e); + } + + try { + // Field.nullable is boolean, so failed/unknown nullable metadata remains false. + field.setNullable(metaData.isNullable(columnIndex) == ResultSetMetaData.columnNullable); + } catch (SQLException e) { + log.debug("read result set nullable metadata failed for column {}", columnIndex, e); + } + try { + field.setAutoIncrement(metaData.isAutoIncrement(columnIndex)); + } catch (Exception e) { + log.debug("read result set auto increment metadata failed for column {}", columnIndex, e); + } + try { + field.setReadOnly(metaData.isReadOnly(columnIndex)); + } catch (Exception e) { + log.debug("read result set read only metadata failed for column {}", columnIndex, e); + } + try { + if (!metaData.isWritable(columnIndex)) { + field.setReadOnly(true); + } + } catch (Exception e) { + log.debug("read result set writable metadata failed for column {}", columnIndex, e); + } + } + + private static String getNullableMetadataValue(MetadataValueReader reader, String name, int columnIndex) { + try { + String value = reader.read(); + return StringUtils.isNotBlank(value) ? value : null; + } catch (SQLException e) { + log.debug("read result set {} metadata failed for column {}", name, columnIndex, e); + return null; + } + } + + @FunctionalInterface + private interface MetadataValueReader { + String read() throws SQLException; + } + // Normalize JDBC driver objects before FastJSON sees them in update_data_view packets. protected Object normalizeJdbcValue(Object value) throws SQLException { + Object normalized = normalizeJdbcDisplayValue(value); + if (normalized != value) { + return normalized; + } if (value == null) { return null; } - if (value instanceof Timestamp timestamp) { - return new Date(timestamp.getTime()); - } - if (value instanceof Long || value instanceof BigDecimal || value instanceof BigInteger) { return value.toString(); } @@ -243,6 +450,69 @@ protected Object normalizeJdbcValue(Object value) throws SQLException { return value; } + static Object normalizeJdbcDisplayValue(Object value) { + if (value == null) { + return null; + } + if (value instanceof Timestamp timestamp) { + var localDateTime = timestamp.toLocalDateTime(); + if (timestamp.getNanos() == 0) { + return localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + return localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.n")); + } + if (value instanceof Time time) { + return time.toLocalTime().toString(); + } + if (value instanceof Date date) { + return date.toLocalDate().toString(); + } + return value; + } + + static void markGeneratedColumns(Connection connection, DbType dbType, List fields) { + if (dbType != DbType.postgresql || connection == null || fields == null || fields.isEmpty()) { + return; + } + String sql = """ + SELECT is_generated, generation_expression + FROM information_schema.columns + WHERE table_schema = ? + AND table_name = ? + AND column_name = ? + """; + for (Field field : fields) { + if (field == null || + StringUtils.isBlank(field.getSchema()) || + StringUtils.isBlank(field.getTable()) || + StringUtils.isBlank(field.getColumnName())) { + continue; + } + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, field.getSchema()); + statement.setString(2, field.getTable()); + statement.setString(3, field.getColumnName()); + try (ResultSet resultSet = statement.executeQuery()) { + if (!resultSet.next()) { + continue; + } + String isGenerated = resultSet.getString("is_generated"); + String generationExpression = resultSet.getString("generation_expression"); + if ("ALWAYS".equalsIgnoreCase(isGenerated) || StringUtils.isNotBlank(generationExpression)) { + field.setGenerated(true); + field.setReadOnly(true); + } + } + } catch (SQLException e) { + log.debug("read postgresql generated column metadata failed for {}.{}.{}", + field.getSchema(), + field.getTable(), + field.getColumnName(), + e); + } + } + } + private String toDisplayArray(java.sql.Array jdbcArray) throws SQLException { try { var text = jdbcArray.toString(); @@ -362,8 +632,14 @@ private void handleDataMasking(SQLQueryResult result) { var maskIndexes = new ArrayList<>(); var maskRules = new HashMap(); for (var i = 0; i < result.getFields().size(); i++) { + var field = result.getFields().get(i); for (Common.DataMaskingRule rule : rules) { - if (this.matchField(result.getFields().get(i), rule.getFieldsPattern())) { + if (this.matchField(field, rule.getFieldsPattern())) { + field.setMasked(true); + field.setEditable(false); + field.setEditReason(EditabilityReason.DATA_MASKED); + field.setInsertable(false); + field.setInsertReason(EditabilityReason.DATA_MASKED); maskIndexes.add(i); maskRules.put(i, rule); } @@ -387,10 +663,13 @@ private void handleDataMasking(SQLQueryResult result) { } private boolean matchField(Field field, String pattern) { - List names = List.of(field.getColumnName(), field.getLabel()); + String[] names = {field.getColumnName(), field.getLabel()}; String[] ps = pattern.split(","); for (String name : names) { + if (name == null) { + continue; + } for (String p : ps) { p = p.trim(); if (p.isEmpty()) continue; @@ -487,6 +766,16 @@ public SQLQueryResult executeWithAudit(SQLExecutePlan plan) throws SQLException } } + @Override + public SQLQueryResult executeRawWithAudit(SQLExecutePlan plan) throws SQLException { + var sess = SessionManager.getCurrentSession(); + try { + return sess.withAudit(plan.getTargetSQL(), () -> this.executeRaw(plan)); + } catch (CommandRejectException e) { + throw new SQLException(e.getMessage()); + } + } + public int count(SQL sql) throws SQLException { return this.count(this.createPlan(sql)); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLHintsHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLHintsHandler.java deleted file mode 100644 index d6e132c..0000000 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/base/BaseSQLHintsHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.jumpserver.chen.framework.datasource.base; - -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.hints.SQLHintsHandler; -import org.jumpserver.chen.framework.session.SessionManager; - -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public abstract class BaseSQLHintsHandler implements SQLHintsHandler { - - public abstract List
getALlTables(String schema) throws SQLException; - - public abstract List getAllFields(String schema) throws SQLException; - - @Override - public Map> getHints(String nodeKey, String context) throws SQLException { - Map> suggestions = new HashMap<>(); - - var session = SessionManager.getCurrentSession(); - if (!session.enableAutoComplete()) { - return suggestions; - } - - var tables = this.getALlTables(context); - var tableNames = tables.stream().map(Table::getName).toList(); - - suggestions.put(context, tableNames); - var fields = this.getAllFields(context); - - tables.forEach(table -> { - var fieldsOfTable = fields.stream().filter(field -> field.getTable().equals(table.getName())).toList(); - suggestions.put(table.getName(), fieldsOfTable.stream().map(Field::getName).toList()); - }); - return suggestions; - } -} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ConnectionOwnership.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ConnectionOwnership.java new file mode 100644 index 0000000..6bcedeb --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ConnectionOwnership.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.edit; + +public enum ConnectionOwnership { + EXECUTION_CONTEXT, + QUERY_CONSOLE +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SaveExecutionContext.java new file mode 100644 index 0000000..2b6d75c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SaveExecutionContext.java @@ -0,0 +1,15 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface SaveExecutionContext extends AutoCloseable { + Connection connection() throws SQLException; + + TransactionMode transactionMode(); + + ConnectionOwnership connectionOwnership(); + + @Override + void close() throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointController.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointController.java new file mode 100644 index 0000000..4747993 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointController.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; + +interface SavepointController { + void create(Connection connection, String name) throws SQLException; + + void rollbackTo(Connection connection, String name) throws SQLException; + + default void release(Connection connection, String name) throws SQLException { + // A dialect without RELEASE SAVEPOINT support may intentionally leave it active. + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointControllers.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointControllers.java new file mode 100644 index 0000000..f8c68a8 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SavepointControllers.java @@ -0,0 +1,41 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import com.alibaba.druid.DbType; + +final class SavepointControllers { + private static final SavepointController STANDARD = new SqlSavepointController( + "SAVEPOINT %s", + "ROLLBACK TO SAVEPOINT %s", + "RELEASE SAVEPOINT %s" + ); + private static final SavepointController ORACLE = new SqlSavepointController( + "SAVEPOINT %s", + "ROLLBACK TO SAVEPOINT %s", + null + ); + private static final SavepointController SQL_SERVER = new SqlSavepointController( + "SAVE TRANSACTION %s", + "ROLLBACK TRANSACTION %s", + null + ); + private static final SavepointController DB2 = new SqlSavepointController( + "SAVEPOINT %s ON ROLLBACK RETAIN CURSORS", + "ROLLBACK TO SAVEPOINT %s", + "RELEASE SAVEPOINT %s" + ); + + private SavepointControllers() { + } + + static SavepointController forDbType(DbType dbType) { + return switch (dbType) { + case postgresql, mysql, mariadb, dm -> STANDARD; + case oracle -> ORACLE; + case sqlserver -> SQL_SERVER; + case db2 -> DB2; + default -> throw new IllegalArgumentException( + "USER_MANAGED DataView savepoints are not supported for database: " + dbType + ); + }; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedSaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedSaveExecutionContext.java new file mode 100644 index 0000000..a756d87 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedSaveExecutionContext.java @@ -0,0 +1,37 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +public final class ServiceManagedSaveExecutionContext implements SaveExecutionContext { + private final Connection connection; + private final ConnectionOwnership connectionOwnership; + + public ServiceManagedSaveExecutionContext(Connection connection, ConnectionOwnership connectionOwnership) { + this.connection = Objects.requireNonNull(connection, "connection"); + this.connectionOwnership = Objects.requireNonNull(connectionOwnership, "connectionOwnership"); + } + + @Override + public Connection connection() { + return this.connection; + } + + @Override + public TransactionMode transactionMode() { + return TransactionMode.SERVICE_MANAGED; + } + + @Override + public ConnectionOwnership connectionOwnership() { + return this.connectionOwnership; + } + + @Override + public void close() throws SQLException { + if (this.connectionOwnership == ConnectionOwnership.EXECUTION_CONTEXT) { + this.connection.close(); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedTransactionBoundary.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedTransactionBoundary.java new file mode 100644 index 0000000..40484e1 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/ServiceManagedTransactionBoundary.java @@ -0,0 +1,142 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.exception.CommitOutcomeUnknownException; +import org.jumpserver.chen.framework.datasource.edit.exception.RollbackFailedException; +import org.jumpserver.chen.framework.datasource.edit.exception.RolledBackConnectionUnavailableException; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Runs a DataView save batch on a connection the service owns (autoCommit=true at entry). + * + * A commit failure is treated as an unknown outcome: the database may have committed despite + * the client never receiving the acknowledgement. Rolling back would either mask a real commit + * or fail meaninglessly, so the connection is discarded and the caller reports the uncertainty + * rather than pretending the batch was rolled back. Failures that occur before commit (a + * statement error, a runtime bug in binding, etc.) have committed nothing and are rolled back + * normally. + */ +@Slf4j +final class ServiceManagedTransactionBoundary implements TransactionBoundary { + @Override + public TransactionOutcome execute( + Connection connection, + TableChangesPlan plan, + TransactionWork work + ) throws SQLException { + boolean originalAutoCommit = connection.getAutoCommit(); + if (!originalAutoCommit) { + throw new SQLException("SERVICE_MANAGED transaction requires autoCommit=true"); + } + + connection.setAutoCommit(false); + T result; + try { + result = work.execute(); + } catch (SQLException | RuntimeException e) { + try { + this.rollback(connection, plan, e); + } catch (RollbackFailedException rollbackFailure) { + throw rollbackFailure; + } + try { + this.restoreAutoCommit(connection, plan, originalAutoCommit); + } catch (SQLException restoreFailure) { + RolledBackConnectionUnavailableException failure = + new RolledBackConnectionUnavailableException(restoreFailure, e); + this.discardConnection(connection, failure); + throw failure; + } + throw e; + } + + try { + this.commit(connection, plan); + } catch (CommitOutcomeUnknownException e) { + this.discardConnection(connection, e); + throw e; + } + + try { + this.restoreAutoCommit(connection, plan, originalAutoCommit); + return TransactionOutcome.success(result, true); + } catch (SQLException restoreFailure) { + this.discardConnection(connection, restoreFailure); + return new TransactionOutcome<>( + result, + true, + true, + TableChangesSaveService.SAVE_CHANGES_COMMITTED_CONNECTION_UNAVAILABLE + ); + } + } + + private void commit(Connection connection, TableChangesPlan plan) throws SQLException { + try { + connection.commit(); + } catch (SQLException e) { + log.error( + "save changes commit failed, table={}.{}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw new CommitOutcomeUnknownException(e); + } + } + + private void rollback(Connection connection, TableChangesPlan plan, Throwable primaryException) + throws RollbackFailedException { + try { + connection.rollback(); + } catch (SQLException e) { + log.warn( + "rollback save changes transaction failed, table={}.{}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + RollbackFailedException failure = new RollbackFailedException(e, primaryException); + this.discardConnection(connection, failure); + throw failure; + } + } + + private void restoreAutoCommit( + Connection connection, + TableChangesPlan plan, + boolean originalAutoCommit + ) throws SQLException { + try { + connection.setAutoCommit(originalAutoCommit); + } catch (SQLException e) { + log.warn( + "restore save changes connection autoCommit failed, table={}.{}, originalAutoCommit={}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + originalAutoCommit, + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw e; + } + } + + private void discardConnection(Connection connection, Throwable failure) { + try { + connection.close(); + } catch (SQLException | RuntimeException e) { + failure.addSuppressed(e); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SqlSavepointController.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SqlSavepointController.java new file mode 100644 index 0000000..5e3386d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/SqlSavepointController.java @@ -0,0 +1,51 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.regex.Pattern; + +final class SqlSavepointController implements SavepointController { + private static final Pattern SAFE_NAME = Pattern.compile("[A-Za-z][A-Za-z0-9_]{0,62}"); + + private final String createSql; + private final String rollbackSql; + private final String releaseSql; + + SqlSavepointController(String createSql, String rollbackSql, String releaseSql) { + this.createSql = createSql; + this.rollbackSql = rollbackSql; + this.releaseSql = releaseSql; + } + + @Override + public void create(Connection connection, String name) throws SQLException { + execute(connection, this.createSql, name); + } + + @Override + public void rollbackTo(Connection connection, String name) throws SQLException { + execute(connection, this.rollbackSql, name); + } + + @Override + public void release(Connection connection, String name) throws SQLException { + validateName(name); + if (this.releaseSql != null) { + execute(connection, this.releaseSql, name); + } + } + + static void validateName(String name) throws SQLException { + if (name == null || !SAFE_NAME.matcher(name).matches()) { + throw new SQLException("Invalid server-generated savepoint name"); + } + } + + private void execute(Connection connection, String template, String name) throws SQLException { + validateName(name); + try (Statement statement = connection.createStatement()) { + statement.execute(template.formatted(name)); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableBrowseSaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableBrowseSaveExecutionContext.java new file mode 100644 index 0000000..be08fd6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableBrowseSaveExecutionContext.java @@ -0,0 +1,41 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Objects; + +public final class TableBrowseSaveExecutionContext implements SaveExecutionContext { + private final ConnectionManager connectionManager; + private Connection connection; + + public TableBrowseSaveExecutionContext(ConnectionManager connectionManager) { + this.connectionManager = Objects.requireNonNull(connectionManager, "connectionManager"); + } + + @Override + public Connection connection() throws SQLException { + if (this.connection == null) { + this.connection = this.connectionManager.getConnection(); + } + return this.connection; + } + + @Override + public TransactionMode transactionMode() { + return TransactionMode.SERVICE_MANAGED; + } + + @Override + public ConnectionOwnership connectionOwnership() { + return ConnectionOwnership.EXECUTION_CONTEXT; + } + + @Override + public void close() throws SQLException { + if (this.connection != null) { + this.connection.close(); + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlan.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlan.java new file mode 100644 index 0000000..1fbf2f5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlan.java @@ -0,0 +1,21 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.Data; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class TableChangesPlan { + private String dataView; + private String schema; + private String table; + private int changeCount; + private int updateCount; + private int insertCount; + private int deleteCount; + private List commands = new ArrayList<>(); + private List auditSqlList = new ArrayList<>(); + private String auditSql; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuildResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuildResult.java new file mode 100644 index 0000000..45329aa --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuildResult.java @@ -0,0 +1,35 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; + +@Data +public class TableChangesPlanBuildResult { + private boolean success; + private String reason; + private Integer failedChangeIndex; + private SaveChangesRequest.ChangeItem failedChange; + private Object failedOperation; + private TableChangesPlan plan; + + public static TableChangesPlanBuildResult success(TableChangesPlan plan) { + TableChangesPlanBuildResult result = new TableChangesPlanBuildResult(); + result.setSuccess(true); + result.setPlan(plan); + return result; + } + + public static TableChangesPlanBuildResult failure(String reason, Integer failedChangeIndex, SaveChangesRequest.ChangeItem failedChange) { + return failure(reason, failedChangeIndex, failedChange, failedChange); + } + + public static TableChangesPlanBuildResult failure(String reason, Integer failedChangeIndex, SaveChangesRequest.ChangeItem failedChange, Object failedOperation) { + TableChangesPlanBuildResult result = new TableChangesPlanBuildResult(); + result.setSuccess(false); + result.setReason(reason); + result.setFailedChangeIndex(failedChangeIndex); + result.setFailedChange(failedChange); + result.setFailedOperation(failedOperation); + return result; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuilder.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuilder.java new file mode 100644 index 0000000..ddcf72d --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPlanBuilder.java @@ -0,0 +1,520 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.datasource.edit.analyzer.EditabilityReason; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditTypeCodecs; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditValueConverter; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; +import org.jumpserver.chen.framework.datasource.edit.dialect.TableEditDialect; +import org.jumpserver.chen.framework.datasource.edit.dialect.TableEditDialects; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class TableChangesPlanBuilder { + public static final int MAX_CHANGES = 500; + public static final String DATABASE_NOT_SUPPORTED_FOR_EDIT = "DATABASE_NOT_SUPPORTED_FOR_EDIT"; + public static final String DATA_VIEW_MISMATCH = "DATA_VIEW_MISMATCH"; + public static final String SCHEMA_TABLE_MISMATCH = "SCHEMA_TABLE_MISMATCH"; + public static final String EMPTY_CHANGES = "EMPTY_CHANGES"; + public static final String TOO_MANY_CHANGES = "TOO_MANY_CHANGES"; + public static final String NO_PRIMARY_KEY = "NO_PRIMARY_KEY"; + public static final String COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED = "COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED"; + public static final String PK_COLUMN_MISMATCH = "PK_COLUMN_MISMATCH"; + public static final String PRIMARY_KEY_VALUE_REQUIRED = "PRIMARY_KEY_VALUE_REQUIRED"; + public static final String PRIMARY_KEY_COLUMN_NOT_EDITABLE = "PRIMARY_KEY_COLUMN_NOT_EDITABLE"; + public static final String SOURCE_COLUMN_NOT_FOUND = "SOURCE_COLUMN_NOT_FOUND"; + public static final String SOURCE_COLUMN_AMBIGUOUS = "SOURCE_COLUMN_AMBIGUOUS"; + public static final String PRIMARY_KEY_SOURCE_COLUMN_MISSING = "PRIMARY_KEY_SOURCE_COLUMN_MISSING"; + public static final String SOURCE_SCHEMA_MISMATCH = "SOURCE_SCHEMA_MISMATCH"; + public static final String SOURCE_TABLE_MISMATCH = "SOURCE_TABLE_MISMATCH"; + public static final String SOURCE_COLUMN_NOT_EDITABLE = "SOURCE_COLUMN_NOT_EDITABLE"; + public static final String NO_OP_CHANGE = "NO_OP_CHANGE"; + public static final String TYPE_CONVERSION_FAILED = "TYPE_CONVERSION_FAILED"; + public static final String ROW_OPERATIONS_TABLE_BROWSE_ONLY = "ROW_OPERATIONS_TABLE_BROWSE_ONLY"; + public static final String INSERT_VALUES_REQUIRED = "INSERT_VALUES_REQUIRED"; + public static final String INSERT_COLUMN_NOT_WRITABLE = "INSERT_COLUMN_NOT_WRITABLE"; + + public TableChangesPlanBuildResult build(TableEditContext context, String actionDataView, SaveChangesRequest request) { + TableEditDialect dialect = TableEditDialects.find(context.getDbType()).orElse(null); + if (dialect == null) { + return TableChangesPlanBuildResult.failure(DATABASE_NOT_SUPPORTED_FOR_EDIT, null, null); + } + if (!StringUtils.equals(actionDataView, context.getDataViewTitle())) { + return TableChangesPlanBuildResult.failure(DATA_VIEW_MISMATCH, null, null); + } + if (request == null || + !StringUtils.equals(request.getSchema(), context.getSchema()) || + !StringUtils.equals(request.getTable(), context.getTable())) { + return TableChangesPlanBuildResult.failure(SCHEMA_TABLE_MISMATCH, null, null); + } + + int updateCount = size(request.getChanges()); + int insertCount = size(request.getInsertRows()); + int deleteCount = size(request.getDeleteRows()); + int totalCount = updateCount + insertCount + deleteCount; + if (totalCount == 0) { + return TableChangesPlanBuildResult.failure(EMPTY_CHANGES, null, null); + } + if (totalCount > MAX_CHANGES) { + return TableChangesPlanBuildResult.failure(TOO_MANY_CHANGES, null, null); + } + if ((insertCount > 0 || deleteCount > 0) && !context.isTableBrowse()) { + return TableChangesPlanBuildResult.failure(ROW_OPERATIONS_TABLE_BROWSE_ONLY, null, null); + } + if (insertCount > 0 && !TableInsertability.isInsertable(context.getFields())) { + return TableChangesPlanBuildResult.failure( + INSERT_COLUMN_NOT_WRITABLE, + deleteCount + updateCount, + null, + request.getInsertRows().get(0) + ); + } + + Field primaryKey = resolveSinglePrimaryKey(context.getFields()); + if (primaryKey == null) { + return TableChangesPlanBuildResult.failure(NO_PRIMARY_KEY, null, null); + } + if (primaryKey == MultiplePrimaryKeys.FIELD) { + return TableChangesPlanBuildResult.failure(COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED, null, null); + } + + String pkColumn = primaryKey.getSourceColumn(); + if (StringUtils.isBlank(pkColumn)) { + return TableChangesPlanBuildResult.failure(PRIMARY_KEY_SOURCE_COLUMN_MISSING, null, null); + } + if (!TableEditTypeCodecs.supports(primaryKey, context.getDbType())) { + return TableChangesPlanBuildResult.failure(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT, null, null); + } + + TableChangesPlan plan = new TableChangesPlan(); + plan.setDataView(context.getDataViewTitle()); + plan.setSchema(context.getSchema()); + plan.setTable(context.getTable()); + plan.setChangeCount(totalCount); + plan.setUpdateCount(updateCount); + plan.setInsertCount(insertCount); + plan.setDeleteCount(deleteCount); + List renderedSqlList = new ArrayList<>(); + + int commandIndex = 0; + BuildCommandsResult deleteResult = buildDeleteCommands(context, request, primaryKey, dialect, renderedSqlList, commandIndex, plan); + if (!deleteResult.success()) { + return deleteResult.failure(); + } + commandIndex += deleteCount; + + BuildCommandsResult updateResult = buildUpdateCommands(context, request, primaryKey, dialect, renderedSqlList, commandIndex, plan); + if (!updateResult.success()) { + return updateResult.failure(); + } + commandIndex += updateCount; + + BuildCommandsResult insertResult = buildInsertCommands(context, request, dialect, renderedSqlList, commandIndex, plan); + if (!insertResult.success()) { + return insertResult.failure(); + } + + plan.setAuditSqlList(List.copyOf(renderedSqlList)); + plan.setAuditSql(String.join("\n", renderedSqlList)); + return TableChangesPlanBuildResult.success(plan); + } + + private BuildCommandsResult buildDeleteCommands( + TableEditContext context, + SaveChangesRequest request, + Field primaryKey, + TableEditDialect dialect, + List renderedSqlList, + int commandIndexOffset, + TableChangesPlan plan + ) { + if (request.getDeleteRows() == null) { + return BuildCommandsResult.ok(); + } + String pkColumn = primaryKey.getSourceColumn(); + for (int i = 0; i < request.getDeleteRows().size(); i++) { + SaveChangesRequest.DeleteRow deleteRow = request.getDeleteRows().get(i); + if (deleteRow == null) { + return BuildCommandsResult.failure(SOURCE_COLUMN_NOT_FOUND, commandIndexOffset + i, null); + } + if (!StringUtils.equals(deleteRow.getPkColumn(), pkColumn)) { + return BuildCommandsResult.failure(PK_COLUMN_MISMATCH, commandIndexOffset + i, deleteRow); + } + if (deleteRow.isPkValueIsNull() || deleteRow.getPkValue() == null) { + return BuildCommandsResult.failure(PRIMARY_KEY_VALUE_REQUIRED, commandIndexOffset + i, deleteRow); + } + + PreparedTableChangeCommand command = new PreparedTableChangeCommand(); + command.setOperation(PreparedTableChangeCommand.Operation.DELETE); + command.setPkColumn(pkColumn); + command.setDeleteRow(deleteRow); + command.setPreparedSql(dialect.buildPreparedDeleteSql(context.getSchema(), context.getTable(), pkColumn)); + try { + PreparedTableChangeCommand.Parameter pkParameter = convertedParameter( + "pkValue", pkColumn, primaryKey, deleteRow.getPkValue(), deleteRow.isPkValueIsNull(), context.getDbType() + ); + command.getParameters().add(pkParameter); + renderedSqlList.add(dialect.buildAuditDeleteSql( + context.getSchema(), + context.getTable(), + pkColumn, + primaryKey, + pkParameter.getValue(), + deleteRow.isPkValueIsNull() + )); + plan.getCommands().add(command); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, deleteRow); + } + } + return BuildCommandsResult.ok(); + } + + private BuildCommandsResult buildUpdateCommands( + TableEditContext context, + SaveChangesRequest request, + Field primaryKey, + TableEditDialect dialect, + List renderedSqlList, + int commandIndexOffset, + TableChangesPlan plan + ) { + if (request.getChanges() == null) { + return BuildCommandsResult.ok(); + } + String pkColumn = primaryKey.getSourceColumn(); + for (int i = 0; i < request.getChanges().size(); i++) { + SaveChangesRequest.ChangeItem change = request.getChanges().get(i); + ChangeValidation validation = validateChange(context, change, primaryKey); + if (validation.reason() != null) { + return BuildCommandsResult.failure(validation.reason(), commandIndexOffset + i, change); + } + + Field targetField = validation.field(); + String sourceColumn = targetField.getSourceColumn(); + PreparedTableChangeCommand command = new PreparedTableChangeCommand(); + command.setOperation(PreparedTableChangeCommand.Operation.UPDATE); + command.setSourceColumn(sourceColumn); + command.setPkColumn(pkColumn); + command.setChange(change); + command.setPreparedSql(dialect.buildPreparedUpdateSql( + context.getSchema(), + context.getTable(), + sourceColumn, + pkColumn + )); + try { + PreparedTableChangeCommand.Parameter newParameter = convertedParameter( + "newValue", sourceColumn, targetField, change.getNewValue(), change.isNewValueIsNull(), context.getDbType() + ); + PreparedTableChangeCommand.Parameter pkParameter = convertedParameter( + "pkValue", pkColumn, primaryKey, change.getPkValue(), change.isPkValueIsNull(), context.getDbType() + ); + PreparedTableChangeCommand.Parameter oldParameter = convertedParameter( + "oldValue", sourceColumn, targetField, change.getOldValue(), change.isOldValueIsNull(), context.getDbType() + ); + command.getParameters().add(newParameter); + command.getParameters().add(pkParameter); + command.getParameters().add(oldParameter); + if (dialect.oldValueParameterCount() > 1) { + command.getParameters().add(oldParameter); + } + renderedSqlList.add(dialect.buildAuditUpdateSql( + context.getSchema(), + context.getTable(), + sourceColumn, + targetField, + newParameter.getValue(), + change.isNewValueIsNull(), + pkColumn, + primaryKey, + pkParameter.getValue(), + change.isPkValueIsNull(), + oldParameter.getValue(), + change.isOldValueIsNull() + )); + plan.getCommands().add(command); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, change); + } + } + return BuildCommandsResult.ok(); + } + + private BuildCommandsResult buildInsertCommands( + TableEditContext context, + SaveChangesRequest request, + TableEditDialect dialect, + List renderedSqlList, + int commandIndexOffset, + TableChangesPlan plan + ) { + if (request.getInsertRows() == null) { + return BuildCommandsResult.ok(); + } + for (int i = 0; i < request.getInsertRows().size(); i++) { + SaveChangesRequest.InsertRow insertRow = request.getInsertRows().get(i); + if (insertRow == null || insertRow.getValues() == null || insertRow.getValues().isEmpty()) { + return BuildCommandsResult.failure(INSERT_VALUES_REQUIRED, commandIndexOffset + i, insertRow); + } + + List sourceColumns = new ArrayList<>(); + List fields = new ArrayList<>(); + List values = new ArrayList<>(); + List valueIsNulls = new ArrayList<>(); + Map requestedValues = insertRow.getValues(); + for (Field field : context.getFields()) { + if (field == null || StringUtils.isBlank(field.getSourceColumn())) { + continue; + } + if (!requestedValues.containsKey(field.getSourceColumn())) { + continue; + } + SaveChangesRequest.CellValue cellValue = requestedValues.get(field.getSourceColumn()); + if (cellValue == null) { + return BuildCommandsResult.failure(INSERT_VALUES_REQUIRED, commandIndexOffset + i, insertRow); + } + InsertValidation validation = validateInsertField(context, field); + if (validation.reason() != null) { + return BuildCommandsResult.failure(validation.reason(), commandIndexOffset + i, insertRow); + } + sourceColumns.add(field.getSourceColumn()); + fields.add(field); + try { + values.add(convertValue(cellValue.getValue(), cellValue.isValueIsNull(), field, context.getDbType())); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, insertRow); + } + valueIsNulls.add(cellValue.isValueIsNull()); + } + + if (sourceColumns.size() != requestedValues.size()) { + return BuildCommandsResult.failure(SOURCE_COLUMN_NOT_FOUND, commandIndexOffset + i, insertRow); + } + if (sourceColumns.isEmpty()) { + return BuildCommandsResult.failure(INSERT_VALUES_REQUIRED, commandIndexOffset + i, insertRow); + } + + PreparedTableChangeCommand command = new PreparedTableChangeCommand(); + command.setOperation(PreparedTableChangeCommand.Operation.INSERT); + command.setInsertRow(insertRow); + command.setPreparedSql(dialect.buildPreparedInsertSql(context.getSchema(), context.getTable(), sourceColumns)); + for (int paramIndex = 0; paramIndex < sourceColumns.size(); paramIndex++) { + command.getParameters().add(new PreparedTableChangeCommand.Parameter( + "insertValue", + sourceColumns.get(paramIndex), + fields.get(paramIndex), + values.get(paramIndex), + valueIsNulls.get(paramIndex), + context.getDbType() + )); + } + plan.getCommands().add(command); + try { + renderedSqlList.add(dialect.buildAuditInsertSql( + context.getSchema(), + context.getTable(), + sourceColumns, + fields, + values, + valueIsNulls + )); + } catch (SQLException e) { + return BuildCommandsResult.failure(TYPE_CONVERSION_FAILED, commandIndexOffset + i, insertRow); + } + } + return BuildCommandsResult.ok(); + } + + private ChangeValidation validateChange(TableEditContext context, SaveChangesRequest.ChangeItem change, Field primaryKey) { + if (change == null) { + return ChangeValidation.failure(SOURCE_COLUMN_NOT_FOUND); + } + if (!StringUtils.equals(change.getPkColumn(), primaryKey.getSourceColumn())) { + return ChangeValidation.failure(PK_COLUMN_MISMATCH); + } + if (change.isPkValueIsNull() || change.getPkValue() == null) { + return ChangeValidation.failure(PRIMARY_KEY_VALUE_REQUIRED); + } + if (change.isOldValueIsNull() == change.isNewValueIsNull() && + Objects.equals(change.getOldValue(), change.getNewValue())) { + return ChangeValidation.failure(NO_OP_CHANGE); + } + if (StringUtils.equals(change.getSourceColumn(), primaryKey.getSourceColumn())) { + return ChangeValidation.failure(PRIMARY_KEY_COLUMN_NOT_EDITABLE); + } + + Field field = resolveUniqueSourceField(context.getFields(), change.getSourceColumn()); + if (field == null) { + return ChangeValidation.failure(SOURCE_COLUMN_NOT_FOUND); + } + if (field == MultiplePrimaryKeys.FIELD) { + return ChangeValidation.failure(SOURCE_COLUMN_AMBIGUOUS); + } + + String sourceReason = validateSourceField(context, field); + if (sourceReason != null) { + return ChangeValidation.failure(sourceReason); + } + if (field.isMasked()) { + return ChangeValidation.failure(EditabilityReason.DATA_MASKED); + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + return ChangeValidation.failure(SOURCE_COLUMN_NOT_EDITABLE); + } + if (field.isPrimaryKey()) { + return ChangeValidation.failure(PRIMARY_KEY_COLUMN_NOT_EDITABLE); + } + if (!field.isEditable()) { + return ChangeValidation.failure(StringUtils.defaultIfBlank(field.getEditReason(), SOURCE_COLUMN_NOT_EDITABLE)); + } + if (!TableEditTypeCodecs.supports(field, context.getDbType())) { + return ChangeValidation.failure(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + } + return ChangeValidation.success(field); + } + + private InsertValidation validateInsertField(TableEditContext context, Field field) { + String sourceReason = validateSourceField(context, field); + if (sourceReason != null) { + return InsertValidation.failure(sourceReason); + } + if (field.isMasked()) { + return InsertValidation.failure(EditabilityReason.DATA_MASKED); + } + if (!field.isInsertable()) { + return InsertValidation.failure(StringUtils.defaultIfBlank(field.getInsertReason(), INSERT_COLUMN_NOT_WRITABLE)); + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + return InsertValidation.failure(INSERT_COLUMN_NOT_WRITABLE); + } + if (!TableEditTypeCodecs.supports(field, context.getDbType())) { + return InsertValidation.failure(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + } + return InsertValidation.success(); + } + + private PreparedTableChangeCommand.Parameter convertedParameter( + String name, + String column, + Field field, + Object value, + boolean valueIsNull, + com.alibaba.druid.DbType dbType + ) throws SQLException { + return new PreparedTableChangeCommand.Parameter( + name, + column, + field, + convertValue(value, valueIsNull, field, dbType), + valueIsNull, + field != null ? field.getJdbcType() : null, + dbType + ); + } + + private Object convertValue(Object value, boolean valueIsNull, Field field, com.alibaba.druid.DbType dbType) throws SQLException { + if (valueIsNull || value == null) { + return null; + } + return TableEditValueConverter.coerce(value, field, dbType); + } + + private String validateSourceField(TableEditContext context, Field field) { + if (StringUtils.isNotBlank(field.getSourceSchema()) && !StringUtils.equals(field.getSourceSchema(), context.getSchema())) { + return SOURCE_SCHEMA_MISMATCH; + } + if (StringUtils.isNotBlank(field.getSourceTable()) && !StringUtils.equals(field.getSourceTable(), context.getTable())) { + return SOURCE_TABLE_MISMATCH; + } + return null; + } + + private Field resolveSinglePrimaryKey(List fields) { + if (fields == null) { + return null; + } + List primaryKeys = fields.stream() + .filter(Objects::nonNull) + .filter(Field::isPrimaryKey) + .toList(); + if (primaryKeys.isEmpty()) { + return null; + } + if (primaryKeys.size() > 1) { + return MultiplePrimaryKeys.FIELD; + } + return primaryKeys.get(0); + } + + private Field resolveUniqueSourceField(List fields, String sourceColumn) { + if (fields == null) { + return null; + } + List matchedFields = fields.stream() + .filter(Objects::nonNull) + .filter(field -> StringUtils.equals(field.getSourceColumn(), sourceColumn)) + .toList(); + if (matchedFields.isEmpty()) { + return null; + } + if (matchedFields.size() > 1) { + return MultiplePrimaryKeys.FIELD; + } + return matchedFields.get(0); + } + + private int size(List list) { + return list == null ? 0 : list.size(); + } + + private record ChangeValidation(String reason, Field field) { + static ChangeValidation success(Field field) { + return new ChangeValidation(null, field); + } + + static ChangeValidation failure(String reason) { + return new ChangeValidation(reason, null); + } + } + + private record InsertValidation(String reason) { + static InsertValidation success() { + return new InsertValidation(null); + } + + static InsertValidation failure(String reason) { + return new InsertValidation(reason); + } + } + + private record BuildCommandsResult(boolean successful, TableChangesPlanBuildResult failureResult) { + boolean success() { + return successful; + } + + TableChangesPlanBuildResult failure() { + return failureResult; + } + + static BuildCommandsResult ok() { + return new BuildCommandsResult(true, null); + } + + static BuildCommandsResult failure(String reason, Integer failedChangeIndex, Object failedOperation) { + SaveChangesRequest.ChangeItem failedChange = failedOperation instanceof SaveChangesRequest.ChangeItem change ? change : null; + return new BuildCommandsResult(false, + TableChangesPlanBuildResult.failure(reason, failedChangeIndex, failedChange, failedOperation)); + } + } + + private static final class MultiplePrimaryKeys { + private static final Field FIELD = new Field(); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPreviewService.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPreviewService.java new file mode 100644 index 0000000..1ba07f2 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesPreviewService.java @@ -0,0 +1,110 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesPreviewResult; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; + +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.UUID; + +public class TableChangesPreviewService { + public static final int MAX_CHANGES = TableChangesPlanBuilder.MAX_CHANGES; + public static final String DATABASE_NOT_SUPPORTED_FOR_EDIT = TableChangesPlanBuilder.DATABASE_NOT_SUPPORTED_FOR_EDIT; + public static final String DATA_VIEW_MISMATCH = TableChangesPlanBuilder.DATA_VIEW_MISMATCH; + public static final String SCHEMA_TABLE_MISMATCH = TableChangesPlanBuilder.SCHEMA_TABLE_MISMATCH; + public static final String EMPTY_CHANGES = TableChangesPlanBuilder.EMPTY_CHANGES; + public static final String TOO_MANY_CHANGES = TableChangesPlanBuilder.TOO_MANY_CHANGES; + public static final String NO_PRIMARY_KEY = TableChangesPlanBuilder.NO_PRIMARY_KEY; + public static final String COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED = TableChangesPlanBuilder.COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED; + public static final String PK_COLUMN_MISMATCH = TableChangesPlanBuilder.PK_COLUMN_MISMATCH; + public static final String PRIMARY_KEY_VALUE_REQUIRED = TableChangesPlanBuilder.PRIMARY_KEY_VALUE_REQUIRED; + public static final String PRIMARY_KEY_COLUMN_NOT_EDITABLE = TableChangesPlanBuilder.PRIMARY_KEY_COLUMN_NOT_EDITABLE; + public static final String SOURCE_COLUMN_NOT_FOUND = TableChangesPlanBuilder.SOURCE_COLUMN_NOT_FOUND; + public static final String SOURCE_COLUMN_AMBIGUOUS = TableChangesPlanBuilder.SOURCE_COLUMN_AMBIGUOUS; + public static final String PRIMARY_KEY_SOURCE_COLUMN_MISSING = TableChangesPlanBuilder.PRIMARY_KEY_SOURCE_COLUMN_MISSING; + public static final String SOURCE_SCHEMA_MISMATCH = TableChangesPlanBuilder.SOURCE_SCHEMA_MISMATCH; + public static final String SOURCE_TABLE_MISMATCH = TableChangesPlanBuilder.SOURCE_TABLE_MISMATCH; + public static final String SOURCE_COLUMN_NOT_EDITABLE = TableChangesPlanBuilder.SOURCE_COLUMN_NOT_EDITABLE; + public static final String NO_OP_CHANGE = TableChangesPlanBuilder.NO_OP_CHANGE; + public static final String TYPE_CONVERSION_FAILED = TableChangesPlanBuilder.TYPE_CONVERSION_FAILED; + public static final String ROW_OPERATIONS_TABLE_BROWSE_ONLY = TableChangesPlanBuilder.ROW_OPERATIONS_TABLE_BROWSE_ONLY; + public static final String INSERT_VALUES_REQUIRED = TableChangesPlanBuilder.INSERT_VALUES_REQUIRED; + public static final String INSERT_COLUMN_NOT_WRITABLE = TableChangesPlanBuilder.INSERT_COLUMN_NOT_WRITABLE; + + private final TableChangesPlanBuilder planBuilder = new TableChangesPlanBuilder(); + + public SaveChangesPreviewResult preview(TableEditContext context, String actionDataView, SaveChangesRequest request) { + SaveChangesPreviewResult result = baseResult(context); + TableChangesPlanBuildResult buildResult = this.planBuilder.build(context, actionDataView, request); + if (!buildResult.isSuccess()) { + result.setReason(buildResult.getReason()); + result.setFailedChangeIndex(buildResult.getFailedChangeIndex()); + result.setFailedChange(buildResult.getFailedChange()); + result.setFailedOperation(buildResult.getFailedOperation()); + return result; + } + + TableChangesPlan plan = buildResult.getPlan(); + result.setSuccess(true); + result.setAllowed(true); + result.setChangeCount(plan.getChangeCount()); + result.setUpdateCount(plan.getUpdateCount()); + result.setInsertCount(plan.getInsertCount()); + result.setDeleteCount(plan.getDeleteCount()); + result.setAuditSql(plan.getAuditSql()); + for (PreparedTableChangeCommand command : plan.getCommands()) { + SaveChangesPreviewResult.PreviewItem previewItem = new SaveChangesPreviewResult.PreviewItem(); + previewItem.setOperation(command.getOperation().name()); + previewItem.setSourceColumn(command.getSourceColumn()); + previewItem.setPkColumn(command.getPkColumn()); + previewItem.setPreparedSql(command.getPreparedSql()); + ArrayList paramsPreview = new ArrayList<>(); + for (PreparedTableChangeCommand.Parameter parameter : command.getParameters()) { + paramsPreview.add(paramPreviewValue(parameter.getValue(), parameter.isValueIsNull())); + } + previewItem.setParamsPreview(paramsPreview); + result.getPreparedStatements().add(previewItem); + } + return result; + } + + private SaveChangesPreviewResult baseResult(TableEditContext context) { + SaveChangesPreviewResult result = new SaveChangesPreviewResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setDataView(context.getDataViewTitle()); + result.setSchema(context.getSchema()); + result.setTable(context.getTable()); + return result; + } + + private Object paramPreviewValue(Object value, boolean isNull) { + if (isNull) { + return null; + } + if (value instanceof BigDecimal decimal) { + return decimal.toPlainString(); + } + if (value instanceof Date date) { + return date.toLocalDate().toString(); + } + if (value instanceof Time time) { + return time.toLocalTime().toString(); + } + if (value instanceof Timestamp timestamp) { + return timestamp.toLocalDateTime().toString(); + } + if (value instanceof OffsetDateTime offsetDateTime) { + return offsetDateTime.toString(); + } + if (value instanceof UUID uuid) { + return uuid.toString(); + } + return value; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesSaveService.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesSaveService.java new file mode 100644 index 0000000..591876f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableChangesSaveService.java @@ -0,0 +1,606 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import com.alibaba.druid.DbType; +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.console.entity.response.SaveChangesResult; +import org.jumpserver.chen.framework.datasource.edit.bind.PreparedStatementBinder; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; +import org.jumpserver.chen.framework.datasource.edit.exception.CommitOutcomeUnknownException; +import org.jumpserver.chen.framework.datasource.edit.exception.OptimisticLockConflictException; +import org.jumpserver.chen.framework.datasource.edit.exception.RollbackFailedException; +import org.jumpserver.chen.framework.datasource.edit.exception.RolledBackConnectionUnavailableException; +import org.jumpserver.chen.framework.datasource.edit.exception.RowNotFoundOrNotUniqueException; +import org.jumpserver.chen.framework.datasource.edit.exception.SavepointRollbackFailedException; +import org.jumpserver.chen.framework.datasource.edit.exception.UnexpectedAffectedRowsException; +import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; +import org.jumpserver.chen.framework.jms.acl.ACLResult; +import org.jumpserver.chen.framework.jms.entity.CommandRecord; +import org.jumpserver.chen.framework.jms.exception.CommandRejectException; +import org.jumpserver.chen.framework.session.Session; +import org.jumpserver.wisp.Common; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +@Slf4j +public class TableChangesSaveService { + public static final String ACL_REJECTED = "ACL_REJECTED"; + public static final String ACL_RISK_LEVEL_UNRECOGNIZED = "ACL_RISK_LEVEL_UNRECOGNIZED"; + public static final String OPTIMISTIC_LOCK_CONFLICT = "OPTIMISTIC_LOCK_CONFLICT"; + public static final String ROW_NOT_FOUND_OR_NOT_UNIQUE = "ROW_NOT_FOUND_OR_NOT_UNIQUE"; + public static final String AFFECTED_ROWS_UNEXPECTED = "AFFECTED_ROWS_UNEXPECTED"; + public static final String SAVE_CHANGES_EXECUTE_FAILED = "SAVE_CHANGES_EXECUTE_FAILED"; + public static final String SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN = "SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN"; + public static final String SAVE_CHANGES_COMMITTED_CONNECTION_UNAVAILABLE = + "SAVE_CHANGES_COMMITTED_CONNECTION_UNAVAILABLE"; + public static final String SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE = + "SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE"; + public static final String SAVE_CHANGES_ROLLBACK_FAILED = "SAVE_CHANGES_ROLLBACK_FAILED"; + public static final String SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED = "SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED"; + public static final String SAVE_CHANGES_AUDIT_REJECTED = "SAVE_CHANGES_AUDIT_REJECTED"; + public static final String SAVE_CHANGES_AUDIT_FAILED_AFTER_COMMIT = "SAVE_CHANGES_AUDIT_FAILED_AFTER_COMMIT"; + public static final String SAVE_CHANGES_AUDIT_FAILED_AFTER_APPLY = "SAVE_CHANGES_AUDIT_FAILED_AFTER_APPLY"; + + private final TableChangesPlanBuilder planBuilder; + private final PreparedStatementBinder binder; + private final ServiceManagedTransactionBoundary serviceManagedTransactionBoundary; + + public TableChangesSaveService() { + this(new TableChangesPlanBuilder(), new PreparedStatementBinder()); + } + + TableChangesSaveService(TableChangesPlanBuilder planBuilder, PreparedStatementBinder binder) { + this.planBuilder = planBuilder; + this.binder = binder; + this.serviceManagedTransactionBoundary = new ServiceManagedTransactionBoundary(); + } + + public SaveChangesResult save( + TableEditContext context, + String actionDataView, + SaveChangesRequest request, + SaveExecutionContext executionContext, + Session session + ) { + SaveChangesResult result = baseResult(context); + TableChangesPlanBuildResult buildResult = this.planBuilder.build(context, actionDataView, request); + if (!buildResult.isSuccess()) { + log.warn( + "save changes plan rejected, reason={}, dataView={}, actionDataView={}, table={}.{}, failedChangeIndex={}", + buildResult.getReason(), + context.getDataViewTitle(), + actionDataView, + context.getSchema(), + context.getTable(), + buildResult.getFailedChangeIndex() + ); + return reject(result, buildResult.getReason(), buildResult.getFailedChangeIndex(), + buildResult.getFailedChange(), buildResult.getFailedOperation()); + } + + TableChangesPlan plan = buildResult.getPlan(); + fillPlanResult(result, plan); + + try { + TransactionBoundary transactionBoundary = transactionBoundary(executionContext, context.getDbType()); + if (executionContext.transactionMode() == TransactionMode.SERVICE_MANAGED) { + try (executionContext) { + return executeAcceptedSave(result, plan, executionContext.connection(), + executionContext.connectionOwnership(), + session, transactionBoundary); + } + } + return executeAcceptedSave(result, plan, executionContext.connection(), + executionContext.connectionOwnership(), + session, transactionBoundary); + } catch (OptimisticLockConflictException e) { + PreparedTableChangeCommand command = commandAt(plan, e.getChangeIndex()); + log.warn( + "save changes optimistic lock conflict, dataView={}, table={}.{}, changeIndex={}, sourceColumn={}, pkColumn={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getChangeIndex(), + command != null ? command.getSourceColumn() : null, + command != null ? command.getPkColumn() : null + ); + return reject(result, OPTIMISTIC_LOCK_CONFLICT, e.getChangeIndex(), + command != null ? command.getChange() : null, command); + } catch (UnexpectedAffectedRowsException e) { + PreparedTableChangeCommand command = commandAt(plan, e.getChangeIndex()); + log.error( + "save changes affected rows unexpected, dataView={}, table={}.{}, changeIndex={}, sourceColumn={}, pkColumn={}, affectedRows={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getChangeIndex(), + command != null ? command.getSourceColumn() : null, + command != null ? command.getPkColumn() : null, + e.getAffectedRows() + ); + return reject(result, AFFECTED_ROWS_UNEXPECTED, e.getChangeIndex(), + command != null ? command.getChange() : null, command); + } catch (RowNotFoundOrNotUniqueException e) { + PreparedTableChangeCommand command = commandAt(plan, e.getChangeIndex()); + log.warn( + "save changes row not found or not unique, dataView={}, table={}.{}, changeIndex={}, operation={}, pkColumn={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getChangeIndex(), + command != null ? command.getOperation() : null, + command != null ? command.getPkColumn() : null + ); + return reject(result, ROW_NOT_FOUND_OR_NOT_UNIQUE, e.getChangeIndex(), + command != null ? command.getChange() : null, command); + } catch (RollbackFailedException e) { + log.error( + "save changes rollback failed and connection was invalidated, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + return rejectWithInvalidatedConnection(result, SAVE_CHANGES_ROLLBACK_FAILED); + } catch (SavepointRollbackFailedException e) { + log.error( + "save changes rollback to savepoint failed and connection was invalidated, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + return rejectWithInvalidatedConnection(result, SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED); + } catch (RolledBackConnectionUnavailableException e) { + log.error( + "save changes rolled back but connection could not be restored, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + return rejectWithInvalidatedConnection( + result, + SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE + ); + } catch (CommitOutcomeUnknownException e) { + // A commit failure is an unknown outcome: the database may have committed despite + // the client error. The connection has already been discarded by the boundary; the + // caller must not let the user treat this as a retriable failure. + log.error( + "save changes commit outcome unknown, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable() + ); + return rejectWithInvalidatedConnection(result, SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN); + } catch (CommandRejectException e) { + log.warn( + "save changes audit rejected, dataView={}, table={}.{}, message={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getMessage(), + e + ); + return reject(result, SAVE_CHANGES_AUDIT_REJECTED, null, null); + } catch (SQLException e) { + // Statement-level detail (changeIndex, columns, sqlState) was already logged by + // executeCommand; record only the save-level summary here. + log.warn( + "save changes failed, reason={}, dataView={}, table={}.{}, sqlState={}, vendorCode={}, message={}", + SAVE_CHANGES_EXECUTE_FAILED, + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e.getSQLState(), + e.getErrorCode(), + e.getMessage() + ); + return reject(result, SAVE_CHANGES_EXECUTE_FAILED, null, null); + } + } + + private SaveChangesResult executeAcceptedSave( + SaveChangesResult result, + TableChangesPlan plan, + Connection connection, + ConnectionOwnership connectionOwnership, + Session session, + TransactionBoundary transactionBoundary + ) throws SQLException, CommandRejectException { + log.debug( + "save changes plan accepted, dataView={}, table={}.{}, changeCount={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + plan.getChangeCount() + ); + + ACLResult aclResult = checkBatchACL(plan, session, connection, connectionOwnership); + if (aclResult.getRiskLevel() == Common.RiskLevel.UNRECOGNIZED) { + log.error( + "save changes acl returned unrecognized risk level, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable() + ); + return reject(result, ACL_RISK_LEVEL_UNRECOGNIZED, null, null); + } + if (isRejected(aclResult)) { + log.warn( + "save changes acl rejected, dataView={}, table={}.{}, riskLevel={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + aclResult.getRiskLevel() + ); + recordRejectedCommand(session, plan, aclResult); + return reject(result, ACL_REJECTED, null, null); + } + ACLResult finalAclResult = aclResult; + AtomicReference> outcomeRef = new AtomicReference<>(); + try { + session.withAudit( + plan.getAuditSql(), + () -> { + TransactionOutcome outcome = executeTransaction( + connection, + plan, + finalAclResult, + transactionBoundary + ); + outcomeRef.set(outcome); + return outcome.value(); + } + ); + } catch (RuntimeException auditFailure) { + TransactionOutcome outcome = outcomeRef.get(); + if (outcome == null) { + throw auditFailure; + } + log.error( + "save changes database work completed but audit failed, dataView={}, table={}.{}, databaseCommitted={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + outcome.databaseCommitted(), + auditFailure + ); + return appliedResult( + result, + plan, + outcome.databaseCommitted(), + false, + outcome.connectionInvalidated(), + outcome.reason() != null + ? outcome.reason() + : outcome.databaseCommitted() + ? SAVE_CHANGES_AUDIT_FAILED_AFTER_COMMIT + : SAVE_CHANGES_AUDIT_FAILED_AFTER_APPLY + ); + } + TransactionOutcome outcome = outcomeRef.get(); + return appliedResult( + result, + plan, + outcome.databaseCommitted(), + true, + outcome.connectionInvalidated(), + outcome.reason() + ); + } + + private SaveChangesResult appliedResult( + SaveChangesResult result, + TableChangesPlan plan, + boolean databaseCommitted, + boolean auditSucceeded, + boolean connectionInvalidated, + String reason + ) { + result.setSuccess(true); + result.setAllowed(true); + result.setDatabaseChangesApplied(true); + result.setDatabaseCommitted(databaseCommitted); + result.setAuditSucceeded(auditSucceeded); + result.setConnectionInvalidated(connectionInvalidated); + result.setReason(reason); + result.setChangeCount(plan.getChangeCount()); + // executeTransaction has already verified every command affected exactly one row. + result.getStatements().forEach(item -> item.setAffectedRows(1)); + + if (auditSucceeded) { + log.info( + "save changes succeeded, dataView={}, table={}.{}, changeCount={}, databaseCommitted={}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + plan.getChangeCount(), + databaseCommitted + ); + } + return result; + } + + private void recordRejectedCommand(Session session, TableChangesPlan plan, ACLResult aclResult) { + CommandRecord commandRecord = new CommandRecord(plan.getAuditSql()); + commandRecord.setRiskLevel(aclResult.getRiskLevel()); + commandRecord.setCmdAclId(aclResult.getCmdAclId()); + commandRecord.setCmdGroupId(aclResult.getCmdGroupId()); + commandRecord.setError(ACL_REJECTED); + try { + session.recordCommand(commandRecord); + } catch (RuntimeException e) { + log.error( + "record rejected save changes command failed, dataView={}, table={}.{}", + plan.getDataView(), + plan.getSchema(), + plan.getTable(), + e + ); + } + } + + private TransactionBoundary transactionBoundary(SaveExecutionContext executionContext, DbType dbType) { + if (executionContext.transactionMode() == TransactionMode.SERVICE_MANAGED) { + return this.serviceManagedTransactionBoundary; + } + if (executionContext.transactionMode() != TransactionMode.USER_MANAGED || + executionContext.connectionOwnership() != ConnectionOwnership.QUERY_CONSOLE) { + throw new IllegalArgumentException("USER_MANAGED transactions require QUERY_CONSOLE connection ownership"); + } + return new UserManagedTransactionBoundary(SavepointControllers.forDbType(dbType)); + } + + TransactionOutcome executeTransaction( + Connection connection, + TableChangesPlan plan, + ACLResult aclResult, + TransactionBoundary transactionBoundary + ) throws SQLException { + return transactionBoundary.execute(connection, plan, () -> { + for (int i = 0; i < plan.getCommands().size(); i++) { + executeCommand(connection, plan, plan.getCommands().get(i), i); + } + return successQueryResult(plan, aclResult); + }); + } + + private SaveChangesResult rejectWithInvalidatedConnection( + SaveChangesResult result, + String reason + ) { + result.setConnectionInvalidated(true); + return reject(result, reason, null, null); + } + + private void executeCommand( + Connection connection, + TableChangesPlan plan, + PreparedTableChangeCommand command, + int changeIndex + ) throws SQLException { + try (PreparedStatement statement = connection.prepareStatement(command.getPreparedSql())) { + this.binder.bind(statement, command); + + int affectedRows; + try { + affectedRows = statement.executeUpdate(); + } catch (SQLException e) { + log.warn( + "save changes statement execute failed, table={}.{}, changeIndex={}, operation={}, sourceColumn={}, pkColumn={}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + changeIndex, + command.getOperation(), + command.getSourceColumn(), + command.getPkColumn(), + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw e; + } + + handleAffectedRows(connection, plan, command, changeIndex, affectedRows); + } + } + + private void handleAffectedRows( + Connection connection, + TableChangesPlan plan, + PreparedTableChangeCommand command, + int changeIndex, + int affectedRows + ) throws SQLException { + if (affectedRows == 0) { + log.warn( + "save changes affected zero rows, table={}.{}, changeIndex={}, operation={}, sourceColumn={}, pkColumn={}", + plan.getSchema(), + plan.getTable(), + changeIndex, + command.getOperation(), + command.getSourceColumn(), + command.getPkColumn() + ); + if (command.getOperation() == PreparedTableChangeCommand.Operation.DELETE) { + throw new RowNotFoundOrNotUniqueException(changeIndex); + } + if (command.getOperation() == PreparedTableChangeCommand.Operation.INSERT) { + throw new UnexpectedAffectedRowsException(changeIndex, affectedRows); + } + throw new OptimisticLockConflictException(changeIndex); + } + if (affectedRows > 1) { + log.error( + "save changes affected unexpected rows, table={}.{}, changeIndex={}, operation={}, sourceColumn={}, pkColumn={}, affectedRows={}", + plan.getSchema(), + plan.getTable(), + changeIndex, + command.getOperation(), + command.getSourceColumn(), + command.getPkColumn(), + affectedRows + ); + throw new UnexpectedAffectedRowsException(changeIndex, affectedRows); + } + } + + private SQLQueryResult successQueryResult(TableChangesPlan plan, ACLResult aclResult) { + SQLQueryResult result = new SQLQueryResult(plan.getAuditSql()); + result.setHasResultSet(false); + result.setUpdateCount(plan.getChangeCount()); + result.setAclResult(aclResult); + return result; + } + + private boolean isRejected(ACLResult aclResult) { + return aclResult != null && + (aclResult.getRiskLevel() == Common.RiskLevel.Reject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewReject || + aclResult.getRiskLevel() == Common.RiskLevel.ReviewCancel); + } + + private ACLResult checkBatchACL( + TableChangesPlan plan, + Session session, + Connection connection, + ConnectionOwnership connectionOwnership + ) { + List results = new ArrayList<>(plan.getAuditSqlList().size()); + boolean batch = plan.getAuditSqlList().size() > 1; + String reviewBatchSql = batch ? plan.getAuditSql() : null; + for (String sql : plan.getAuditSqlList()) { + results.add(session.checkACLWithContext( + sql, + ACLCommandContext.planned( + connection, + connectionOwnership, + plan.getChangeCount(), + reviewBatchSql + ) + )); + } + return aggregateACLResults(results); + } + + static ACLResult aggregateACLResults(List results) { + ACLResult aggregate = normalACLResult(); + boolean notify = false; + boolean hasEffectiveResult = false; + for (ACLResult result : results) { + if (result == null) { + continue; + } + notify |= result.isNotify(); + int resultPriority = riskPriority(result.getRiskLevel()); + int aggregatePriority = riskPriority(aggregate.getRiskLevel()); + if (!hasEffectiveResult || resultPriority > aggregatePriority || + (resultPriority == aggregatePriority && result.isNotify() && !aggregate.isNotify())) { + aggregate = copyACLResult(result); + hasEffectiveResult = true; + } + } + aggregate.setNotify(notify); + return aggregate; + } + + private static int riskPriority(Common.RiskLevel riskLevel) { + if (riskLevel == null) { + return 0; + } + return switch (riskLevel) { + case UNRECOGNIZED -> 7; + case ReviewReject -> 6; + case ReviewCancel -> 5; + case Reject -> 4; + case ReviewAccept -> 3; + case Warning -> 2; + case Normal -> 1; + }; + } + + private static ACLResult normalACLResult() { + ACLResult result = new ACLResult(); + result.setRiskLevel(Common.RiskLevel.Normal); + return result; + } + + private static ACLResult copyACLResult(ACLResult source) { + ACLResult result = new ACLResult(); + result.setRiskLevel(source.getRiskLevel()); + result.setCmdAclId(source.getCmdAclId()); + result.setCmdGroupId(source.getCmdGroupId()); + result.setNotify(source.isNotify()); + return result; + } + + private SaveChangesResult baseResult(TableEditContext context) { + SaveChangesResult result = new SaveChangesResult(); + result.setSuccess(false); + result.setAllowed(false); + result.setDataView(context.getDataViewTitle()); + result.setSchema(context.getSchema()); + result.setTable(context.getTable()); + return result; + } + + private void fillPlanResult(SaveChangesResult result, TableChangesPlan plan) { + result.setDataView(plan.getDataView()); + result.setSchema(plan.getSchema()); + result.setTable(plan.getTable()); + result.setChangeCount(plan.getChangeCount()); + result.setUpdateCount(plan.getUpdateCount()); + result.setInsertCount(plan.getInsertCount()); + result.setDeleteCount(plan.getDeleteCount()); + result.setAuditSql(plan.getAuditSql()); + for (PreparedTableChangeCommand command : plan.getCommands()) { + SaveChangesResult.ResultItem item = new SaveChangesResult.ResultItem(); + item.setOperation(command.getOperation().name()); + item.setSourceColumn(command.getSourceColumn()); + item.setPkColumn(command.getPkColumn()); + item.setPreparedSql(command.getPreparedSql()); + result.getStatements().add(item); + } + } + + private PreparedTableChangeCommand commandAt(TableChangesPlan plan, int index) { + if (index < 0 || index >= plan.getCommands().size()) { + return null; + } + return plan.getCommands().get(index); + } + + private SaveChangesResult reject( + SaveChangesResult result, + String reason, + Integer failedChangeIndex, + SaveChangesRequest.ChangeItem failedChange + ) { + return reject(result, reason, failedChangeIndex, failedChange, failedChange); + } + + private SaveChangesResult reject( + SaveChangesResult result, + String reason, + Integer failedChangeIndex, + SaveChangesRequest.ChangeItem failedChange, + Object failedOperation + ) { + result.setSuccess(false); + result.setAllowed(false); + result.setReason(reason); + result.setFailedChangeIndex(failedChangeIndex); + result.setFailedChange(failedChange); + result.setFailedOperation(failedOperation); + return result; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableEditContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableEditContext.java new file mode 100644 index 0000000..6db2132 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableEditContext.java @@ -0,0 +1,37 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import com.alibaba.druid.DbType; +import lombok.Data; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.List; + +@Data +public class TableEditContext { + private String dataViewTitle; + private String schema; + private String table; + private List fields; + private DbType dbType; + private boolean tableBrowse; + + public TableEditContext(String dataViewTitle, String schema, String table, List fields, DbType dbType) { + this(dataViewTitle, schema, table, fields, dbType, false); + } + + public TableEditContext( + String dataViewTitle, + String schema, + String table, + List fields, + DbType dbType, + boolean tableBrowse + ) { + this.dataViewTitle = dataViewTitle; + this.schema = schema; + this.table = table; + this.fields = fields; + this.dbType = dbType; + this.tableBrowse = tableBrowse; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableInsertability.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableInsertability.java new file mode 100644 index 0000000..24ff75e --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TableInsertability.java @@ -0,0 +1,31 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.List; + +public final class TableInsertability { + private TableInsertability() { + } + + public static boolean isInsertable(List fields) { + if (fields == null || fields.isEmpty()) { + return false; + } + + boolean hasOrdinaryField = false; + for (Field field : fields) { + if (field == null) { + return false; + } + if (field.isAutoIncrement() || field.isGenerated()) { + continue; + } + hasOrdinaryField = true; + if (!field.isInsertable()) { + return false; + } + } + return hasOrdinaryField; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionBoundary.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionBoundary.java new file mode 100644 index 0000000..af4b372 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionBoundary.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.sql.SQLException; + +interface TransactionBoundary { + TransactionOutcome execute( + Connection connection, + TableChangesPlan plan, + TransactionWork work + ) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionMode.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionMode.java new file mode 100644 index 0000000..b3bb0c1 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionMode.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.edit; + +public enum TransactionMode { + SERVICE_MANAGED, + USER_MANAGED +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionOutcome.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionOutcome.java new file mode 100644 index 0000000..5e3004b --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionOutcome.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit; + +record TransactionOutcome( + T value, + boolean databaseCommitted, + boolean connectionInvalidated, + String reason +) { + static TransactionOutcome success(T value, boolean databaseCommitted) { + return new TransactionOutcome<>(value, databaseCommitted, false, null); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionWork.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionWork.java new file mode 100644 index 0000000..b36e22c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/TransactionWork.java @@ -0,0 +1,9 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.SQLException; + +@FunctionalInterface +interface TransactionWork { + T execute() throws SQLException; +} + diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedSaveExecutionContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedSaveExecutionContext.java new file mode 100644 index 0000000..a537b7a --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedSaveExecutionContext.java @@ -0,0 +1,32 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import java.sql.Connection; +import java.util.Objects; + +public final class UserManagedSaveExecutionContext implements SaveExecutionContext { + private final Connection connection; + + public UserManagedSaveExecutionContext(Connection connection) { + this.connection = Objects.requireNonNull(connection, "connection"); + } + + @Override + public Connection connection() { + return this.connection; + } + + @Override + public TransactionMode transactionMode() { + return TransactionMode.USER_MANAGED; + } + + @Override + public ConnectionOwnership connectionOwnership() { + return ConnectionOwnership.QUERY_CONSOLE; + } + + @Override + public void close() { + // The surrounding user transaction owns the connection lifecycle. + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedTransactionBoundary.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedTransactionBoundary.java new file mode 100644 index 0000000..1a3769a --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/UserManagedTransactionBoundary.java @@ -0,0 +1,67 @@ +package org.jumpserver.chen.framework.datasource.edit; + +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.exception.SavepointRollbackFailedException; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicLong; + +@Slf4j +final class UserManagedTransactionBoundary implements TransactionBoundary { + private static final AtomicLong SAVEPOINT_SEQUENCE = new AtomicLong(); + + private final SavepointController savepointController; + + UserManagedTransactionBoundary(SavepointController savepointController) { + this.savepointController = savepointController; + } + + @Override + public TransactionOutcome execute( + Connection connection, + TableChangesPlan plan, + TransactionWork work + ) throws SQLException { + String savepointName = "chen_dataview_" + SAVEPOINT_SEQUENCE.incrementAndGet(); + this.savepointController.create(connection, savepointName); + try { + T result = work.execute(); + this.savepointController.release(connection, savepointName); + return TransactionOutcome.success(result, false); + } catch (SQLException | RuntimeException e) { + rollbackToSavepoint(connection, plan, savepointName, e); + throw e; + } + } + + private void rollbackToSavepoint( + Connection connection, + TableChangesPlan plan, + String savepointName, + Throwable primaryException + ) throws SavepointRollbackFailedException { + try { + this.savepointController.rollbackTo(connection, savepointName); + } catch (SQLException rollbackException) { + log.warn( + "rollback DataView savepoint failed, table={}.{}, savepoint={}, sqlState={}, vendorCode={}, message={}", + plan.getSchema(), + plan.getTable(), + savepointName, + rollbackException.getSQLState(), + rollbackException.getErrorCode(), + rollbackException.getMessage(), + rollbackException + ); + SavepointRollbackFailedException failure = + new SavepointRollbackFailedException(rollbackException, primaryException); + try { + connection.close(); + } catch (SQLException | RuntimeException closeException) { + failure.addSuppressed(closeException); + } + throw failure; + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/EditabilityReason.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/EditabilityReason.java new file mode 100644 index 0000000..544cd4b --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/EditabilityReason.java @@ -0,0 +1,33 @@ +package org.jumpserver.chen.framework.datasource.edit.analyzer; + +public final class EditabilityReason { + public static final String NOT_SELECT = "NOT_SELECT"; + public static final String MULTIPLE_STATEMENTS_NOT_SUPPORTED = "MULTIPLE_STATEMENTS_NOT_SUPPORTED"; + public static final String QUERY_BLOCK_NOT_SUPPORTED = "QUERY_BLOCK_NOT_SUPPORTED"; + public static final String CTE_NOT_SUPPORTED = "CTE_NOT_SUPPORTED"; + public static final String SET_OPERATION_NOT_SUPPORTED = "SET_OPERATION_NOT_SUPPORTED"; + public static final String JOIN_NOT_SUPPORTED = "JOIN_NOT_SUPPORTED"; + public static final String SUBQUERY_NOT_SUPPORTED = "SUBQUERY_NOT_SUPPORTED"; + public static final String TABLE_SOURCE_NOT_SUPPORTED = "TABLE_SOURCE_NOT_SUPPORTED"; + public static final String VIEW_NOT_SUPPORTED = "VIEW_NOT_SUPPORTED"; + public static final String DISTINCT_NOT_SUPPORTED = "DISTINCT_NOT_SUPPORTED"; + public static final String GROUP_BY_NOT_SUPPORTED = "GROUP_BY_NOT_SUPPORTED"; + public static final String HAVING_NOT_SUPPORTED = "HAVING_NOT_SUPPORTED"; + public static final String EXPRESSION_COLUMN = "EXPRESSION_COLUMN"; + public static final String AGGREGATE_OR_FUNCTION_COLUMN = "AGGREGATE_OR_FUNCTION_COLUMN"; + public static final String ALL_COLUMNS_NOT_SUPPORTED = "ALL_COLUMNS_NOT_SUPPORTED"; + public static final String COLUMN_OWNER_NOT_SUPPORTED = "COLUMN_OWNER_NOT_SUPPORTED"; + public static final String UNKNOWN_COLUMN_SOURCE = "UNKNOWN_COLUMN_SOURCE"; + public static final String SELECT_LIST_MISMATCH = "SELECT_LIST_MISMATCH"; + public static final String PRIMARY_KEY_RESOLUTION_NOT_IMPLEMENTED = "PRIMARY_KEY_RESOLUTION_NOT_IMPLEMENTED"; + public static final String PRIMARY_KEY_RESOLUTION_FAILED = "PRIMARY_KEY_RESOLUTION_FAILED"; + public static final String NO_PRIMARY_KEY = "NO_PRIMARY_KEY"; + public static final String COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED = "COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED"; + public static final String PRIMARY_KEY_NOT_IN_RESULT = "PRIMARY_KEY_NOT_IN_RESULT"; + public static final String PRIMARY_KEY_COLUMN_NOT_EDITABLE = "PRIMARY_KEY_COLUMN_NOT_EDITABLE"; + public static final String DATA_MASKED = "DATA_MASKED"; + public static final String TYPE_NOT_SUPPORTED_FOR_EDIT = "TYPE_NOT_SUPPORTED_FOR_EDIT"; + + private EditabilityReason() { + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/QueryResultEditabilityAnalyzer.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/QueryResultEditabilityAnalyzer.java new file mode 100644 index 0000000..af9cb91 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/analyzer/QueryResultEditabilityAnalyzer.java @@ -0,0 +1,507 @@ +package org.jumpserver.chen.framework.datasource.edit.analyzer; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; +import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause; +import com.alibaba.druid.sql.ast.statement.SQLSelectItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUnionQuery; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.edit.TableChangesPlanBuilder; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditTypeCodecs; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; +import org.jumpserver.chen.framework.datasource.edit.pk.PrimaryKeyResolution; +import org.jumpserver.chen.framework.datasource.edit.pk.PrimaryKeyResolver; + +import java.sql.SQLException; +import java.util.List; +import java.util.Objects; + +@Slf4j +public class QueryResultEditabilityAnalyzer { + private final DbType dbType; + private final PrimaryKeyResolver primaryKeyResolver; + + public QueryResultEditabilityAnalyzer(DbType dbType, PrimaryKeyResolver primaryKeyResolver) { + this.dbType = dbType; + this.primaryKeyResolver = primaryKeyResolver; + } + + public void analyze(String sourceSQL, List fields) { + if (fields == null || fields.isEmpty()) { + return; + } + + try { + List statements = SQLUtils.parseStatements(sourceSQL, this.dbType); + if (statements.size() != 1) { + this.markAllReadOnly(fields, EditabilityReason.MULTIPLE_STATEMENTS_NOT_SUPPORTED); + return; + } + if (!(statements.get(0) instanceof SQLSelectStatement selectStatement)) { + this.markAllReadOnly(fields, EditabilityReason.NOT_SELECT); + return; + } + this.analyzeSelect(selectStatement, fields); + } catch (Exception e) { + log.debug("analyze query result editability failed", e); + this.markAllReadOnly(fields, EditabilityReason.UNKNOWN_COLUMN_SOURCE); + } + } + + private void analyzeSelect(SQLSelectStatement selectStatement, List fields) { + SQLSelectQueryBlock queryBlock = this.resolveSupportedQueryBlock(selectStatement, fields); + if (queryBlock == null) { + return; + } + + SourceTable source = this.resolveSourceTable(queryBlock, fields); + + if (!this.mapSelectItems( + queryBlock.getSelectList(), + fields, + source.schema(), + source.table(), + source.alias() + )) { + return; + } + + this.applyPrimaryKeyEditability(fields, source.schema(), source.table()); + } + + private SQLSelectQueryBlock resolveSupportedQueryBlock(SQLSelectStatement selectStatement, List fields) { + var select = selectStatement.getSelect(); + if (select.getWithSubQuery() != null) { + this.markAllReadOnly(fields, EditabilityReason.CTE_NOT_SUPPORTED); + return null; + } + if (select.getQuery() instanceof SQLUnionQuery) { + this.markAllReadOnly(fields, EditabilityReason.SET_OPERATION_NOT_SUPPORTED); + return null; + } + if (!(select.getQuery() instanceof SQLSelectQueryBlock queryBlock)) { + this.markAllReadOnly(fields, EditabilityReason.QUERY_BLOCK_NOT_SUPPORTED); + return null; + } + + if (!this.validateQueryBlock(queryBlock, fields)) { + return null; + } + return queryBlock; + } + + private SourceTable resolveSourceTable(SQLSelectQueryBlock queryBlock, List fields) { + var tableSource = (SQLExprTableSource) queryBlock.getFrom(); + String rawSchema = tableSource.getSchema(); + String rawTable = tableSource.getTableName(); + String schema = normalizeIdentifier(rawSchema); + String table = normalizeIdentifier(rawTable); + String tableAlias = normalizeIdentifier(tableSource.getAlias()); + schema = this.canonicalizeSourceSchema(schema, fields, isQuotedIdentifier(rawSchema)); + table = this.canonicalizeSourceTable(table, fields, isQuotedIdentifier(rawTable)); + + return new SourceTable(schema, table, tableAlias); + } + + private boolean validateQueryBlock(SQLSelectQueryBlock queryBlock, List fields) { + if (queryBlock.isDistinct()) { + this.markAllReadOnly(fields, EditabilityReason.DISTINCT_NOT_SUPPORTED); + return false; + } + + SQLSelectGroupByClause groupBy = queryBlock.getGroupBy(); + if (groupBy != null && !groupBy.getItems().isEmpty()) { + this.markAllReadOnly(fields, EditabilityReason.GROUP_BY_NOT_SUPPORTED); + return false; + } + if (groupBy != null && groupBy.getHaving() != null) { + this.markAllReadOnly(fields, EditabilityReason.HAVING_NOT_SUPPORTED); + return false; + } + + SQLTableSource from = queryBlock.getFrom(); + if (from instanceof SQLJoinTableSource) { + this.markAllReadOnly(fields, EditabilityReason.JOIN_NOT_SUPPORTED); + return false; + } + if (from instanceof SQLSubqueryTableSource) { + this.markAllReadOnly(fields, EditabilityReason.SUBQUERY_NOT_SUPPORTED); + return false; + } + if (!(from instanceof SQLExprTableSource)) { + this.markAllReadOnly(fields, EditabilityReason.TABLE_SOURCE_NOT_SUPPORTED); + return false; + } + if (!containsAllColumn(queryBlock.getSelectList()) && queryBlock.getSelectList().size() != fields.size()) { + this.markAllReadOnly(fields, EditabilityReason.SELECT_LIST_MISMATCH); + return false; + } + return true; + } + + private boolean mapSelectItems(List selectItems, + List fields, + String schema, + String table, + String tableAlias) { + if (containsAllColumn(selectItems)) { + return this.mapAllColumns(selectItems, fields, schema, table, tableAlias); + } + + for (int i = 0; i < selectItems.size(); i++) { + var field = fields.get(i); + var column = resolveColumn(selectItems.get(i).getExpr(), table, tableAlias); + if (column.reason() != null) { + this.markAllReadOnly(fields, column.reason()); + return false; + } + + field.setSourceSchema(schema); + field.setSourceTable(table); + field.setSourceColumn(this.canonicalizeSourceColumn(field, column.name(), column.quoted())); + } + return true; + } + + private boolean mapAllColumns(List selectItems, + List fields, + String schema, + String table, + String tableAlias) { + if (selectItems.size() != 1) { + this.markAllReadOnly(fields, EditabilityReason.ALL_COLUMNS_NOT_SUPPORTED); + return false; + } + + var star = selectItems.get(0).getExpr(); + String owner = starOwner(star); + if (owner == null) { + this.markAllReadOnly(fields, EditabilityReason.ALL_COLUMNS_NOT_SUPPORTED); + return false; + } + if (StringUtils.isNotBlank(owner) && !matchesTableOwner(owner, table, tableAlias)) { + this.markAllReadOnly(fields, EditabilityReason.COLUMN_OWNER_NOT_SUPPORTED); + return false; + } + + for (Field field : fields) { + String sourceColumn = this.canonicalizeSourceColumn(field, normalizeIdentifier(field.getColumnName()), false); + if (StringUtils.isBlank(sourceColumn)) { + this.markAllReadOnly(fields, EditabilityReason.UNKNOWN_COLUMN_SOURCE); + return false; + } + field.setSourceSchema(schema); + field.setSourceTable(table); + field.setSourceColumn(sourceColumn); + } + return true; + } + + private ColumnResolution resolveColumn(SQLExpr expr, String table, String tableAlias) { + if (expr instanceof SQLIdentifierExpr identifierExpr) { + String rawName = identifierExpr.getName(); + return ColumnResolution.column(normalizeIdentifier(rawName), isQuotedIdentifier(rawName)); + } + if (expr instanceof SQLPropertyExpr propertyExpr) { + String owner = normalizeIdentifier(propertyExpr.getOwnerName()); + if (!matchesTableOwner(owner, table, tableAlias)) { + return ColumnResolution.reason(EditabilityReason.COLUMN_OWNER_NOT_SUPPORTED); + } + String rawName = propertyExpr.getName(); + return ColumnResolution.column(normalizeIdentifier(rawName), isQuotedIdentifier(rawName)); + } + if (expr instanceof SQLAggregateExpr || expr instanceof SQLMethodInvokeExpr) { + return ColumnResolution.reason(EditabilityReason.AGGREGATE_OR_FUNCTION_COLUMN); + } + return ColumnResolution.reason(EditabilityReason.EXPRESSION_COLUMN); + } + + private static boolean containsAllColumn(List selectItems) { + return selectItems.stream() + .map(SQLSelectItem::getExpr) + .anyMatch(QueryResultEditabilityAnalyzer::isAllColumn); + } + + private static boolean isAllColumn(SQLExpr expr) { + if (expr instanceof SQLAllColumnExpr) { + return true; + } + if (expr instanceof SQLPropertyExpr propertyExpr) { + return Objects.equals(normalizeIdentifier(propertyExpr.getName()), "*"); + } + return false; + } + + private static String starOwner(SQLExpr expr) { + if (expr instanceof SQLAllColumnExpr allColumnExpr) { + SQLExpr owner = allColumnExpr.getOwner(); + return owner == null ? "" : normalizeIdentifier(owner.toString()); + } + if (expr instanceof SQLPropertyExpr propertyExpr && Objects.equals(normalizeIdentifier(propertyExpr.getName()), "*")) { + return normalizeIdentifier(propertyExpr.getOwnerName()); + } + return null; + } + + private void applyPrimaryKeyEditability(List fields, String schema, String table) { + PrimaryKeyResolution resolution; + try { + resolution = this.primaryKeyResolver.resolvePrimaryKeys(schema, table); + } catch (SQLException e) { + log.debug("resolve primary key failed for {}.{}", schema, table, e); + this.markAllReadOnly(fields, EditabilityReason.PRIMARY_KEY_RESOLUTION_FAILED); + return; + } + + if (resolution == null) { + this.markAllReadOnly(fields, EditabilityReason.PRIMARY_KEY_RESOLUTION_NOT_IMPLEMENTED); + return; + } + if (StringUtils.isNotBlank(resolution.getReadOnlyReason())) { + this.markAllReadOnly(fields, resolution.getReadOnlyReason()); + return; + } + + List primaryKeys = resolution.getPrimaryKeys(); + if (primaryKeys.isEmpty()) { + this.markAllReadOnly(fields, EditabilityReason.NO_PRIMARY_KEY); + return; + } + if (primaryKeys.size() > 1) { + this.markAllReadOnly(fields, EditabilityReason.COMPOSITE_PRIMARY_KEY_NOT_SUPPORTED); + return; + } + + String primaryKey = normalizeIdentifier(primaryKeys.get(0)); + var primaryKeyField = fields.stream() + .filter(field -> identifierEquals(field.getSourceColumn(), primaryKey)) + .findFirst(); + if (primaryKeyField.isEmpty()) { + this.markAllReadOnly(fields, EditabilityReason.PRIMARY_KEY_NOT_IN_RESULT); + return; + } + + fields.forEach(field -> { + this.applyColumnEditability(field, primaryKey); + this.applyInsertability(field); + }); + } + + private void applyColumnEditability(Field field, String primaryKey) { + if (identifierEquals(field.getSourceColumn(), primaryKey)) { + field.setPrimaryKey(true); + field.setEditable(false); + field.setEditReason(EditabilityReason.PRIMARY_KEY_COLUMN_NOT_EDITABLE); + return; + } + if (field.isMasked()) { + field.setEditable(false); + field.setEditReason(EditabilityReason.DATA_MASKED); + return; + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + field.setEditable(false); + field.setEditReason(TableChangesPlanBuilder.SOURCE_COLUMN_NOT_EDITABLE); + return; + } + if (!TableEditTypeCodecs.supports(field, this.dbType)) { + field.setEditable(false); + field.setEditReason(EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + return; + } + field.setEditable(true); + field.setEditReason(null); + } + + private void markAllReadOnly(List fields, String reason) { + fields.forEach(field -> { + field.setEditable(false); + field.setEditReason(reason); + field.setInsertable(false); + field.setInsertReason(reason); + field.setRequiredOnInsert(false); + }); + } + + private void applyInsertability(Field field) { + if (field.isMasked()) { + this.markNotInsertable(field, EditabilityReason.DATA_MASKED); + return; + } + if (!TableEditTypeCodecs.supports(field, this.dbType)) { + this.markNotInsertable(field, EditabilityReason.TYPE_NOT_SUPPORTED_FOR_EDIT); + return; + } + if (field.isAutoIncrement() || field.isReadOnly() || field.isGenerated()) { + this.markNotInsertable(field, TableChangesPlanBuilder.INSERT_COLUMN_NOT_WRITABLE); + return; + } + if (StringUtils.isBlank(field.getSourceColumn())) { + this.markNotInsertable(field, EditabilityReason.UNKNOWN_COLUMN_SOURCE); + return; + } + field.setInsertable(true); + field.setInsertReason(null); + field.setRequiredOnInsert(!field.isNullable()); + } + + private void markNotInsertable(Field field, String reason) { + field.setInsertable(false); + field.setInsertReason(reason); + field.setRequiredOnInsert(false); + } + + private static boolean matchesTableOwner(String owner, String table, String tableAlias) { + if (StringUtils.isBlank(owner)) { + return true; + } + return identifierEquals(owner, tableAlias) || identifierEquals(owner, table); + } + + private static boolean identifierEquals(String left, String right) { + return StringUtils.equalsIgnoreCase(normalizeIdentifier(left), normalizeIdentifier(right)); + } + + private static String normalizeIdentifier(String identifier) { + if (identifier == null) { + return null; + } + String value = identifier.trim(); + if ((value.startsWith("\"") && value.endsWith("\"")) || + (value.startsWith("`") && value.endsWith("`")) || + (value.startsWith("[") && value.endsWith("]"))) { + value = value.substring(1, value.length() - 1); + } + return value; + } + + private String canonicalizeSourceSchema(String schema, List fields, boolean quoted) { + if (!this.shouldCanonicalizeFromMetadata()) { + return schema; + } + return this.canonicalizeFromFieldMetadata(schema, fields, MetadataKind.SCHEMA, quoted); + } + + private String canonicalizeSourceTable(String table, List fields, boolean quoted) { + if (!this.shouldCanonicalizeFromMetadata()) { + return table; + } + return this.canonicalizeFromFieldMetadata(table, fields, MetadataKind.TABLE, quoted); + } + + private String canonicalizeSourceColumn(Field field, String column, boolean quoted) { + if (!this.shouldCanonicalizeFromMetadata() || field == null) { + return column; + } + if (quoted && this.isDamengOrDb2()) { + return column; + } + String metadataColumn = normalizeIdentifier(field.getColumnName()); + if (StringUtils.isBlank(metadataColumn)) { + return column; + } + if (StringUtils.isBlank(column) || identifierEquals(metadataColumn, column)) { + return metadataColumn; + } + return this.canonicalizeUnquotedIdentifier(column, quoted); + } + + private String canonicalizeFromFieldMetadata(String identifier, List fields, MetadataKind kind, boolean quoted) { + if (quoted && this.isDamengOrDb2()) { + return identifier; + } + if (fields == null || fields.isEmpty()) { + return this.canonicalizeUnquotedIdentifier(identifier, quoted); + } + String normalizedIdentifier = normalizeIdentifier(identifier); + String fallback = null; + for (Field field : fields) { + if (field == null) { + continue; + } + String metadataIdentifier = switch (kind) { + case SCHEMA -> normalizeIdentifier(field.getSchema()); + case TABLE -> normalizeIdentifier(field.getTable()); + }; + if (StringUtils.isBlank(metadataIdentifier)) { + continue; + } + if (fallback == null) { + fallback = metadataIdentifier; + } + if (StringUtils.isBlank(normalizedIdentifier) || identifierEquals(metadataIdentifier, normalizedIdentifier)) { + return metadataIdentifier; + } + } + if (StringUtils.isNotBlank(fallback) && StringUtils.isBlank(normalizedIdentifier)) { + return fallback; + } + return this.canonicalizeUnquotedIdentifier(normalizedIdentifier, quoted); + } + + private String canonicalizeUnquotedIdentifier(String identifier, boolean quoted) { + if (quoted || StringUtils.isBlank(identifier)) { + return identifier; + } + if (this.dbType != DbType.oracle && this.dbType != DbType.dm && this.dbType != DbType.db2) { + return identifier; + } + return identifier.toUpperCase(java.util.Locale.ROOT); + } + + private static boolean isQuotedIdentifier(String identifier) { + if (identifier == null) { + return false; + } + String value = identifier.trim(); + return (value.startsWith("\"") && value.endsWith("\"")) || + (value.startsWith("`") && value.endsWith("`")) || + (value.startsWith("[") && value.endsWith("]")); + } + + private boolean shouldCanonicalizeFromMetadata() { + return this.dbType == DbType.oracle || + this.dbType == DbType.sqlserver || + this.dbType == DbType.dm || + this.dbType == DbType.db2; + } + + private boolean isDamengOrDb2() { + return this.dbType == DbType.dm || this.dbType == DbType.db2; + } + + private enum MetadataKind { + SCHEMA, + TABLE + } + + private record ColumnResolution(String name, boolean quoted, String reason) { + static ColumnResolution column(String name, boolean quoted) { + if (StringUtils.isBlank(name) || Objects.equals(name, "*")) { + return reason(EditabilityReason.UNKNOWN_COLUMN_SOURCE); + } + return new ColumnResolution(name, quoted, null); + } + + static ColumnResolution reason(String reason) { + return new ColumnResolution(null, false, reason); + } + } + + private record SourceTable(String schema, String table, String alias) { + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/PreparedStatementBinder.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/PreparedStatementBinder.java new file mode 100644 index 0000000..8cafbaf --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/PreparedStatementBinder.java @@ -0,0 +1,68 @@ +package org.jumpserver.chen.framework.datasource.edit.bind; + +import com.alibaba.druid.DbType; +import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.command.PreparedTableChangeCommand; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + +@Slf4j +public class PreparedStatementBinder { + + public void bind(PreparedStatement statement, PreparedTableChangeCommand command) throws SQLException { + for (int i = 0; i < command.getParameters().size(); i++) { + PreparedTableChangeCommand.Parameter parameter = command.getParameters().get(i); + bindConvertedValue( + statement, + i + 1, + parameter + ); + } + } + + private void bindConvertedValue( + PreparedStatement statement, + int index, + PreparedTableChangeCommand.Parameter parameter + ) throws SQLException { + try { + if (parameter.isValueIsNull() || parameter.getValue() == null) { + statement.setNull(index, parameter.getJdbcType() != null ? parameter.getJdbcType() : Types.NULL); + return; + } + if (isJsonOther(parameter)) { + statement.setObject(index, parameter.getValue(), Types.OTHER); + return; + } + statement.setObject(index, parameter.getValue()); + } catch (SQLException e) { + log.warn( + "bind save changes parameter failed, index={}, paramName={}, column={}, isNull={}, jdbcType={}, dbType={}, valueClass={}, sqlState={}, vendorCode={}, message={}", + index, + parameter.getName(), + parameter.getColumn(), + parameter.isValueIsNull(), + parameter.getJdbcType(), + parameter.getDbType(), + parameter.getValue() != null ? parameter.getValue().getClass().getName() : null, + e.getSQLState(), + e.getErrorCode(), + e.getMessage(), + e + ); + throw e; + } + } + + private boolean isJsonOther(PreparedTableChangeCommand.Parameter parameter) { + if (parameter.getDbType() != DbType.postgresql || + parameter.getJdbcType() == null || + parameter.getJdbcType() != Types.OTHER) { + return false; + } + String type = TableEditTypeCodecs.normalizeType(parameter.getField()); + return "json".equals(type) || "jsonb".equals(type); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditTypeCodecs.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditTypeCodecs.java new file mode 100644 index 0000000..6b4c3cb --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditTypeCodecs.java @@ -0,0 +1,523 @@ +package org.jumpserver.chen.framework.datasource.edit.bind; + +import com.alibaba.druid.DbType; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.UUID; + +public final class TableEditTypeCodecs { + private static final Gson GSON = new Gson(); + private static final int SQLSERVER_GUID = -145; + private static final int SQLSERVER_SMALLDATETIME = -150; + private static final int SQLSERVER_DATETIME = -151; + private static final int SQLSERVER_DATETIMEOFFSET = -155; + private static final int ORACLE_TIMESTAMPNS = -100; + private static final int ORACLE_TIMESTAMPTZ = -101; + private static final int ORACLE_TIMESTAMPLTZ = -102; + private static final int ORACLE_JSON = 2016; + + private TableEditTypeCodecs() { + } + + public static boolean supports(Field field) { + return supports(field, null); + } + + public static boolean supports(Field field, DbType dbType) { + return kind(field, dbType) != Kind.UNSUPPORTED; + } + + public static Object coerce(Object value, Field field) throws SQLException { + return coerce(value, field, null); + } + + public static Object coerce(Object value, Field field, DbType dbType) throws SQLException { + Kind kind = kind(field, dbType); + String type = normalizeType(field); + if (kind == Kind.UNSUPPORTED) { + throw new SQLException("unsupported table edit type, column=" + + (field != null ? field.getSourceColumn() : null) + ", type=" + type); + } + if (value == null) { + return null; + } + + try { + return switch (kind) { + case TINYINT -> toByte(value); + case SMALLINT -> toShort(value); + case INTEGER -> toInteger(value); + case BIGINT -> toLong(value); + case DECIMAL -> toBigDecimal(value); + case FLOATING -> toFiniteDouble(value); + case BOOLEAN -> toBoolean(value); + case STRING -> value.toString(); + case DATE -> toDate(value); + case TIME -> toTime(value); + case TIMESTAMP -> toTimestamp(value); + case OFFSET_TIMESTAMP -> toOffsetDateTime(value); + case UUID -> toUuid(value); + case JSON -> toJsonString(value); + case UNSUPPORTED -> throw new IllegalArgumentException("unsupported type"); + }; + } catch (RuntimeException e) { + String column = field != null ? field.getSourceColumn() : null; + throw new SQLException("convert value failed, column=" + column + ", type=" + type, e); + } + } + + public static String renderLiteral(Object value, boolean isNull, Field field) throws SQLException { + return renderLiteral(value, isNull, field, null); + } + + public static String renderLiteral(Object value, boolean isNull, Field field, DbType dbType) throws SQLException { + if (isNull || value == null) { + return "NULL"; + } + Object coercedValue = coerce(value, field, dbType); + if (coercedValue == null) { + return "NULL"; + } + if (coercedValue instanceof Number) { + return coercedValue.toString(); + } + if (coercedValue instanceof Boolean bool) { + return bool ? "TRUE" : "FALSE"; + } + return "'" + coercedValue.toString().replace("'", "''") + "'"; + } + + public static String normalizeType(Field field) { + if (field == null || StringUtils.isBlank(field.getType())) { + return null; + } + String type = unwrapKnownTypeWrappers(field.getType().trim().toLowerCase()); + return type.replaceAll("\\([^)]*\\)", "").trim(); + } + + private static Kind kind(Field field, DbType dbType) { + if (field == null) { + return Kind.UNSUPPORTED; + } + String type = normalizeType(field); + Integer jdbcType = field.getJdbcType(); + if (isUnsignedType(type)) { + return Kind.UNSUPPORTED; + } + if (jdbcType != null) { + if (jdbcType == SQLSERVER_GUID) { + return dbType == DbType.sqlserver ? Kind.UUID : Kind.UNSUPPORTED; + } + if (jdbcType == SQLSERVER_DATETIMEOFFSET) { + return dbType == DbType.sqlserver ? Kind.OFFSET_TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_TIMESTAMPTZ) { + return dbType == DbType.oracle ? Kind.OFFSET_TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_TIMESTAMPLTZ) { + return Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_JSON) { + return dbType == DbType.oracle ? Kind.JSON : Kind.UNSUPPORTED; + } + if (jdbcType == Types.TIMESTAMP_WITH_TIMEZONE) { + return timestampWithTimezoneKind(type, dbType); + } + if (jdbcType == Types.BOOLEAN) { + return booleanKind(type, dbType); + } + if (jdbcType == Types.BIT) { + return bitKind(type, dbType); + } + if (jdbcType == Types.OTHER && StringUtils.isNotBlank(type)) { + if (isTimestampWithTimezone(type)) { + return timestampWithTimezoneKind(type, dbType); + } + if (isUuid(type)) { + return uuidKind(type, dbType); + } + if (isJson(type)) { + return jsonKind(type, dbType); + } + if (isBoolean(type)) { + return booleanKind(type, dbType); + } + } + if (jdbcType == SQLSERVER_DATETIME) { + return dbType == DbType.sqlserver ? Kind.TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == SQLSERVER_SMALLDATETIME) { + return dbType == DbType.sqlserver ? Kind.TIMESTAMP : Kind.UNSUPPORTED; + } + if (jdbcType == ORACLE_TIMESTAMPNS) { + return dbType == DbType.oracle ? Kind.TIMESTAMP : Kind.UNSUPPORTED; + } + } + if (jdbcType != null && jdbcType != Types.OTHER) { + return switch (jdbcType) { + case Types.TINYINT -> Kind.TINYINT; + case Types.SMALLINT -> Kind.SMALLINT; + case Types.INTEGER -> Kind.INTEGER; + case Types.BIGINT -> Kind.BIGINT; + case Types.NUMERIC, Types.DECIMAL -> Kind.DECIMAL; + case Types.REAL, Types.FLOAT, Types.DOUBLE -> Kind.FLOATING; + case Types.CHAR, Types.VARCHAR, Types.LONGVARCHAR, + Types.NCHAR, Types.NVARCHAR, Types.LONGNVARCHAR -> Kind.STRING; + case Types.DATE -> Kind.DATE; + case Types.TIME -> Kind.TIME; + case Types.TIMESTAMP -> Kind.TIMESTAMP; + default -> Kind.UNSUPPORTED; + }; + } + if (StringUtils.isBlank(type)) { + return Kind.UNSUPPORTED; + } + if (isBitString(type)) { + return Kind.UNSUPPORTED; + } + if (isBigInt(type)) { + return Kind.BIGINT; + } + if (isInteger(type)) { + return Kind.INTEGER; + } + if (isTinyInt(type)) { + return Kind.TINYINT; + } + if (isSmallInt(type)) { + return Kind.SMALLINT; + } + if (isDecimal(type)) { + return Kind.DECIMAL; + } + if (isFloating(type)) { + return Kind.FLOATING; + } + if (isBoolean(type)) { + return booleanKind(type, dbType); + } + if (isString(type)) { + return Kind.STRING; + } + if (isDate(type)) { + return Kind.DATE; + } + if (isTime(type)) { + return Kind.TIME; + } + if (isTimestamp(type)) { + return Kind.TIMESTAMP; + } + if (isTimestampWithTimezone(type)) { + return timestampWithTimezoneKind(type, dbType); + } + if (isUuid(type)) { + return uuidKind(type, dbType); + } + if (isJson(type)) { + return jsonKind(type, dbType); + } + return Kind.UNSUPPORTED; + } + + private static Kind booleanKind(String type, DbType dbType) { + if (dbType == DbType.postgresql || dbType == DbType.mysql || dbType == DbType.mariadb) { + return StringUtils.isBlank(type) || isBoolean(type) ? Kind.BOOLEAN : Kind.UNSUPPORTED; + } + return Kind.UNSUPPORTED; + } + + private static Kind bitKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && isBoolean(type)) { + return Kind.BOOLEAN; + } + if (dbType == DbType.sqlserver && (StringUtils.isBlank(type) || StringUtils.equals(type, "bit"))) { + return Kind.BOOLEAN; + } + return Kind.UNSUPPORTED; + } + + private static Kind uuidKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && StringUtils.equals(type, "uuid")) { + return Kind.UUID; + } + if (dbType == DbType.sqlserver && StringUtils.equals(type, "uniqueidentifier")) { + return Kind.UUID; + } + return Kind.UNSUPPORTED; + } + + private static Kind jsonKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && StringUtils.equalsAny(type, "json", "jsonb")) { + return Kind.JSON; + } + if ((dbType == DbType.mysql || dbType == DbType.mariadb || dbType == DbType.oracle) && + StringUtils.equals(type, "json")) { + return Kind.JSON; + } + return Kind.UNSUPPORTED; + } + + private static Kind timestampWithTimezoneKind(String type, DbType dbType) { + if (dbType == DbType.postgresql && (StringUtils.isBlank(type) || StringUtils.equals(type, "timestamptz") || + StringUtils.equals(type, "timestamp with time zone"))) { + return Kind.OFFSET_TIMESTAMP; + } + if (dbType == DbType.sqlserver && (StringUtils.isBlank(type) || StringUtils.equals(type, "datetimeoffset"))) { + return Kind.OFFSET_TIMESTAMP; + } + if (dbType == DbType.oracle && (StringUtils.isBlank(type) || + StringUtils.equals(type, "timestamp with time zone"))) { + return Kind.OFFSET_TIMESTAMP; + } + return Kind.UNSUPPORTED; + } + + private static String unwrapKnownTypeWrappers(String type) { + String unwrapped = type; + boolean changed; + do { + changed = false; + if (unwrapped.startsWith("nullable(") && unwrapped.endsWith(")")) { + unwrapped = unwrapped.substring("nullable(".length(), unwrapped.length() - 1).trim(); + changed = true; + } + if (unwrapped.startsWith("lowcardinality(") && unwrapped.endsWith(")")) { + unwrapped = unwrapped.substring("lowcardinality(".length(), unwrapped.length() - 1).trim(); + changed = true; + } + } while (changed); + return unwrapped; + } + + private static boolean isBigInt(String type) { + return StringUtils.equalsAny(type, "bigserial", "bigint", "int8"); + } + + private static boolean isInteger(String type) { + return StringUtils.equalsAny(type, "serial", "integer", "int", "int4", "mediumint"); + } + + private static boolean isTinyInt(String type) { + return StringUtils.equalsAny(type, "tinyint", "int1"); + } + + private static boolean isSmallInt(String type) { + return StringUtils.equalsAny(type, "smallint", "int2"); + } + + private static boolean isDecimal(String type) { + return StringUtils.equalsAny(type, "numeric", "decimal", "number"); + } + + private static boolean isFloating(String type) { + return StringUtils.equalsAny(type, "real", "float", "float4", "float8", "double", "double precision"); + } + + private static boolean isUnsignedType(String type) { + return StringUtils.endsWith(type, " unsigned") || StringUtils.startsWith(type, "uint"); + } + + private static boolean isBoolean(String type) { + return StringUtils.equalsAny(type, "bool", "boolean"); + } + + private static boolean isBitString(String type) { + return StringUtils.equalsAny(type, "bit", "bit varying", "varbit"); + } + + private static boolean isString(String type) { + return StringUtils.equalsAny(type, "varchar", "varchar2", "character varying", "text", "tinytext", + "mediumtext", "longtext", "char", "character", "bpchar", "nvarchar", "nvarchar2", "nchar", + "longnvarchar"); + } + + private static boolean isDate(String type) { + return StringUtils.equals(type, "date"); + } + + private static boolean isTime(String type) { + return StringUtils.equalsAny(type, "time", "time without time zone"); + } + + private static boolean isTimestamp(String type) { + return StringUtils.equalsAny(type, "timestamp", "timestamp without time zone", "datetime", "datetime2", + "smalldatetime"); + } + + private static boolean isTimestampWithTimezone(String type) { + return StringUtils.equalsAny(type, "timestamptz", "timestamp with time zone", "datetimeoffset"); + } + + private static boolean isUuid(String type) { + return StringUtils.equalsAny(type, "uuid", "uniqueidentifier"); + } + + private static boolean isJson(String type) { + return StringUtils.equalsAny(type, "json", "jsonb"); + } + + private static Byte toByte(Object value) { + return exactInteger(value, BigInteger.valueOf(Byte.MIN_VALUE), BigInteger.valueOf(Byte.MAX_VALUE)).byteValueExact(); + } + + private static Short toShort(Object value) { + return exactInteger(value, BigInteger.valueOf(Short.MIN_VALUE), BigInteger.valueOf(Short.MAX_VALUE)).shortValueExact(); + } + + private static Integer toInteger(Object value) { + return exactInteger(value, BigInteger.valueOf(Integer.MIN_VALUE), BigInteger.valueOf(Integer.MAX_VALUE)).intValueExact(); + } + + private static Long toLong(Object value) { + return exactInteger(value, BigInteger.valueOf(Long.MIN_VALUE), BigInteger.valueOf(Long.MAX_VALUE)).longValueExact(); + } + + private static BigInteger exactInteger(Object value, BigInteger minimum, BigInteger maximum) { + BigInteger integer = toBigDecimal(value).toBigIntegerExact(); + if (integer.compareTo(minimum) < 0 || integer.compareTo(maximum) > 0) { + throw new ArithmeticException("integer out of range"); + } + return integer; + } + + private static BigDecimal toBigDecimal(Object value) { + if (value instanceof BigDecimal bigDecimal) { + return bigDecimal; + } + if (value instanceof Number || value instanceof CharSequence) { + return new BigDecimal(value.toString()); + } + throw new IllegalArgumentException("value is not numeric"); + } + + private static Double toFiniteDouble(Object value) { + double converted; + if (value instanceof Number number) { + converted = number.doubleValue(); + } else { + converted = Double.parseDouble(value.toString().trim()); + } + if (!Double.isFinite(converted)) { + throw new IllegalArgumentException("value is not finite"); + } + return converted; + } + + private static Boolean toBoolean(Object value) { + if (value instanceof Boolean bool) { + return bool; + } + String text = value.toString().trim(); + if (StringUtils.equalsAnyIgnoreCase(text, "true", "t", "1", "yes", "y")) { + return true; + } + if (StringUtils.equalsAnyIgnoreCase(text, "false", "f", "0", "no", "n")) { + return false; + } + throw new IllegalArgumentException("value is not boolean"); + } + + private static Date toDate(Object value) { + if (value instanceof Date date) { + return date; + } + if (value instanceof java.util.Date date) { + return new Date(date.getTime()); + } + String text = value.toString().trim(); + int timeSeparator = Math.max(text.indexOf('T'), text.indexOf(' ')); + if (timeSeparator > -1) { + text = text.substring(0, timeSeparator); + } + return Date.valueOf(LocalDate.parse(text)); + } + + private static Time toTime(Object value) { + if (value instanceof Time time) { + return time; + } + if (value instanceof java.util.Date date) { + return new Time(date.getTime()); + } + String text = value.toString().trim(); + if (text.endsWith("Z") || text.matches(".*[+-]\\d{2}:\\d{2}$")) { + return Time.valueOf(OffsetDateTime.parse(text).toLocalTime()); + } + if (text.contains("T") || text.contains(" ")) { + return Time.valueOf(LocalDateTime.parse(text.replace(' ', 'T')).toLocalTime()); + } + return Time.valueOf(LocalTime.parse(text)); + } + + private static Timestamp toTimestamp(Object value) { + if (value instanceof Timestamp timestamp) { + return timestamp; + } + if (value instanceof java.util.Date date) { + return new Timestamp(date.getTime()); + } + String text = value.toString().trim(); + if (text.endsWith("Z") || text.matches(".*[+-]\\d{2}:\\d{2}$")) { + return Timestamp.from(OffsetDateTime.parse(text).toInstant()); + } + return Timestamp.valueOf(LocalDateTime.parse(text.replace(' ', 'T'))); + } + + private static OffsetDateTime toOffsetDateTime(Object value) { + if (value instanceof OffsetDateTime offsetDateTime) { + return offsetDateTime; + } + return OffsetDateTime.parse(value.toString().trim()); + } + + private static UUID toUuid(Object value) { + if (value instanceof UUID uuid) { + return uuid; + } + return UUID.fromString(value.toString().trim()); + } + + private static String toJsonString(Object value) { + String text = value instanceof CharSequence ? value.toString() : GSON.toJson(value); + try { + JsonParser.parseString(text); + } catch (JsonParseException e) { + throw new IllegalArgumentException("value is not valid json", e); + } + return text; + } + + private enum Kind { + TINYINT, + SMALLINT, + INTEGER, + BIGINT, + DECIMAL, + FLOATING, + BOOLEAN, + STRING, + DATE, + TIME, + TIMESTAMP, + OFFSET_TIMESTAMP, + UUID, + JSON, + UNSUPPORTED + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditValueConverter.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditValueConverter.java new file mode 100644 index 0000000..c74bc6c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/bind/TableEditValueConverter.java @@ -0,0 +1,19 @@ +package org.jumpserver.chen.framework.datasource.edit.bind; + +import com.alibaba.druid.DbType; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.sql.SQLException; + +public final class TableEditValueConverter { + private TableEditValueConverter() { + } + + public static Object coerce(Object value, Field field) throws SQLException { + return TableEditTypeCodecs.coerce(value, field); + } + + public static Object coerce(Object value, Field field, DbType dbType) throws SQLException { + return TableEditTypeCodecs.coerce(value, field, dbType); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/command/PreparedTableChangeCommand.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/command/PreparedTableChangeCommand.java new file mode 100644 index 0000000..be493e6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/command/PreparedTableChangeCommand.java @@ -0,0 +1,60 @@ +package org.jumpserver.chen.framework.datasource.edit.command; + +import com.alibaba.druid.DbType; +import lombok.Data; +import org.jumpserver.chen.framework.console.entity.request.SaveChangesRequest; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class PreparedTableChangeCommand { + private Operation operation; + private String preparedSql; + private String sourceColumn; + private String pkColumn; + private SaveChangesRequest.ChangeItem change; + private SaveChangesRequest.InsertRow insertRow; + private SaveChangesRequest.DeleteRow deleteRow; + private List parameters = new ArrayList<>(); + + public enum Operation { + UPDATE, + DELETE, + INSERT + } + + @Data + public static class Parameter { + private final String name; + private final String column; + private final Field field; + private final Object value; + private final boolean valueIsNull; + private final Integer jdbcType; + private final DbType dbType; + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull) { + this(name, column, field, value, valueIsNull, field != null ? field.getJdbcType() : null, null); + } + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull, DbType dbType) { + this(name, column, field, value, valueIsNull, field != null ? field.getJdbcType() : null, dbType); + } + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull, Integer jdbcType) { + this(name, column, field, value, valueIsNull, jdbcType, null); + } + + public Parameter(String name, String column, Field field, Object value, boolean valueIsNull, Integer jdbcType, DbType dbType) { + this.name = name; + this.column = column; + this.field = field; + this.value = value; + this.valueIsNull = valueIsNull; + this.jdbcType = jdbcType; + this.dbType = dbType; + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/AbstractTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/AbstractTableEditDialect.java new file mode 100644 index 0000000..9412846 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/AbstractTableEditDialect.java @@ -0,0 +1,149 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.entity.resource.Field; +import org.jumpserver.chen.framework.datasource.edit.bind.TableEditTypeCodecs; +import org.jumpserver.chen.framework.datasource.sql.SQLIdentifier; + +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +abstract class AbstractTableEditDialect implements TableEditDialect { + private final DbType dbType; + + AbstractTableEditDialect(DbType dbType) { + this.dbType = dbType; + } + + @Override + public String quoteIdentifier(String identifier) { + return SQLIdentifier.quote(this.dbType, identifier); + } + + @Override + public String buildPreparedUpdateSql(String schema, String table, String sourceColumn, String pkColumn) { + String qualifiedTable = this.qualifiedTable(schema, table); + String quotedSourceColumn = this.quoteIdentifier(sourceColumn); + String quotedPkColumn = this.quoteIdentifier(pkColumn); + return "UPDATE " + qualifiedTable + "\n" + + "SET " + quotedSourceColumn + " = ?\n" + + "WHERE " + quotedPkColumn + " = ?\n" + + " AND " + this.buildPreparedOldValueCondition(quotedSourceColumn); + } + + @Override + public String buildPreparedDeleteSql(String schema, String table, String pkColumn) { + return "DELETE FROM " + this.qualifiedTable(schema, table) + "\n" + + "WHERE " + this.quoteIdentifier(pkColumn) + " = ?"; + } + + @Override + public String buildPreparedInsertSql(String schema, String table, List sourceColumns) { + String columns = sourceColumns.stream() + .map(this::quoteIdentifier) + .collect(Collectors.joining(", ")); + String placeholders = sourceColumns.stream() + .map(column -> "?") + .collect(Collectors.joining(", ")); + return "INSERT INTO " + this.qualifiedTable(schema, table) + " (" + columns + ")\n" + + "VALUES (" + placeholders + ")"; + } + + @Override + public int oldValueParameterCount() { + return 1; + } + + @Override + public String renderLiteral(Object value, boolean isNull, Field field) throws SQLException { + return TableEditTypeCodecs.renderLiteral(value, isNull, field, this.dbType); + } + + @Override + public String buildAuditUpdateSql( + String schema, + String table, + String sourceColumn, + Field targetField, + Object newValue, + boolean newValueIsNull, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull, + Object oldValue, + boolean oldValueIsNull + ) throws SQLException { + if (targetField != null && StringUtils.isNotBlank(targetField.getSourceColumn()) && + !StringUtils.equals(targetField.getSourceColumn(), sourceColumn)) { + throw new SQLException("sourceColumn does not match targetField.sourceColumn"); + } + String qualifiedTable = this.qualifiedTable(schema, table); + String quotedSourceColumn = this.quoteIdentifier(sourceColumn); + String quotedPkColumn = this.quoteIdentifier(pkColumn); + String renderedOldValue = this.renderLiteral(oldValue, oldValueIsNull, targetField); + return "UPDATE " + qualifiedTable + "\n" + + "SET " + quotedSourceColumn + " = " + this.renderLiteral(newValue, newValueIsNull, targetField) + "\n" + + "WHERE " + quotedPkColumn + " = " + this.renderLiteral(pkValue, pkValueIsNull, pkField) + "\n" + + " AND " + this.buildAuditOldValueCondition(quotedSourceColumn, renderedOldValue) + ";"; + } + + @Override + public String buildAuditDeleteSql( + String schema, + String table, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull + ) throws SQLException { + return "DELETE FROM " + this.qualifiedTable(schema, table) + "\n" + + "WHERE " + this.quoteIdentifier(pkColumn) + " = " + + this.renderLiteral(pkValue, pkValueIsNull, pkField) + ";"; + } + + @Override + public String buildAuditInsertSql( + String schema, + String table, + List sourceColumns, + List fields, + List values, + List valueIsNulls + ) throws SQLException { + String columns = sourceColumns.stream() + .map(this::quoteIdentifier) + .collect(Collectors.joining(", ")); + StringBuilder renderedValues = new StringBuilder(); + for (int i = 0; i < sourceColumns.size(); i++) { + if (i > 0) { + renderedValues.append(", "); + } + renderedValues.append(this.renderLiteral(values.get(i), valueIsNulls.get(i), fields.get(i))); + } + return "INSERT INTO " + this.qualifiedTable(schema, table) + " (" + columns + ")\n" + + "VALUES (" + renderedValues + ");"; + } + + protected abstract String buildPreparedOldValueCondition(String quotedSourceColumn); + + protected abstract String buildAuditOldValueCondition(String quotedSourceColumn, String renderedOldValue); + + protected String buildPreparedNullableEqualityCondition(String quotedSourceColumn) { + return "(" + quotedSourceColumn + " = ? OR (" + quotedSourceColumn + " IS NULL AND ? IS NULL))"; + } + + protected String buildAuditNullableEqualityCondition(String quotedSourceColumn, String renderedOldValue) { + return "(" + quotedSourceColumn + " = " + renderedOldValue + " OR (" + + quotedSourceColumn + " IS NULL AND " + renderedOldValue + " IS NULL))"; + } + + protected String qualifiedTable(String schema, String table) { + if (StringUtils.isBlank(schema)) { + return this.quoteIdentifier(table); + } + return this.quoteIdentifier(schema) + "." + this.quoteIdentifier(table); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/DamengTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/DamengTableEditDialect.java new file mode 100644 index 0000000..e3e2180 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/DamengTableEditDialect.java @@ -0,0 +1,24 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class DamengTableEditDialect extends AbstractTableEditDialect { + DamengTableEditDialect() { + super(DbType.dm); + } + + @Override + public int oldValueParameterCount() { + return 2; + } + + @Override + protected String buildPreparedOldValueCondition(String quotedSourceColumn) { + return this.buildPreparedNullableEqualityCondition(quotedSourceColumn); + } + + @Override + protected String buildAuditOldValueCondition(String quotedSourceColumn, String renderedOldValue) { + return this.buildAuditNullableEqualityCondition(quotedSourceColumn, renderedOldValue); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/Db2TableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/Db2TableEditDialect.java new file mode 100644 index 0000000..71d070c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/Db2TableEditDialect.java @@ -0,0 +1,24 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class Db2TableEditDialect extends AbstractTableEditDialect { + Db2TableEditDialect() { + super(DbType.db2); + } + + @Override + public int oldValueParameterCount() { + return 2; + } + + @Override + protected String buildPreparedOldValueCondition(String quotedSourceColumn) { + return this.buildPreparedNullableEqualityCondition(quotedSourceColumn); + } + + @Override + protected String buildAuditOldValueCondition(String quotedSourceColumn, String renderedOldValue) { + return this.buildAuditNullableEqualityCondition(quotedSourceColumn, renderedOldValue); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/MysqlTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/MysqlTableEditDialect.java new file mode 100644 index 0000000..2bf8503 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/MysqlTableEditDialect.java @@ -0,0 +1,19 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class MysqlTableEditDialect extends AbstractTableEditDialect { + MysqlTableEditDialect() { + super(DbType.mysql); + } + + @Override + protected String buildPreparedOldValueCondition(String quotedSourceColumn) { + return quotedSourceColumn + " <=> ?"; + } + + @Override + protected String buildAuditOldValueCondition(String quotedSourceColumn, String renderedOldValue) { + return quotedSourceColumn + " <=> " + renderedOldValue; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/OracleTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/OracleTableEditDialect.java new file mode 100644 index 0000000..d6da7a9 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/OracleTableEditDialect.java @@ -0,0 +1,24 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class OracleTableEditDialect extends AbstractTableEditDialect { + OracleTableEditDialect() { + super(DbType.oracle); + } + + @Override + public int oldValueParameterCount() { + return 2; + } + + @Override + protected String buildPreparedOldValueCondition(String quotedSourceColumn) { + return this.buildPreparedNullableEqualityCondition(quotedSourceColumn); + } + + @Override + protected String buildAuditOldValueCondition(String quotedSourceColumn, String renderedOldValue) { + return this.buildAuditNullableEqualityCondition(quotedSourceColumn, renderedOldValue); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/PostgresqlTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/PostgresqlTableEditDialect.java new file mode 100644 index 0000000..158769f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/PostgresqlTableEditDialect.java @@ -0,0 +1,19 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class PostgresqlTableEditDialect extends AbstractTableEditDialect { + PostgresqlTableEditDialect() { + super(DbType.postgresql); + } + + @Override + protected String buildPreparedOldValueCondition(String quotedSourceColumn) { + return quotedSourceColumn + " IS NOT DISTINCT FROM ?"; + } + + @Override + protected String buildAuditOldValueCondition(String quotedSourceColumn, String renderedOldValue) { + return quotedSourceColumn + " IS NOT DISTINCT FROM " + renderedOldValue; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/SqlServerTableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/SqlServerTableEditDialect.java new file mode 100644 index 0000000..5b922f5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/SqlServerTableEditDialect.java @@ -0,0 +1,24 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +class SqlServerTableEditDialect extends AbstractTableEditDialect { + SqlServerTableEditDialect() { + super(DbType.sqlserver); + } + + @Override + public int oldValueParameterCount() { + return 2; + } + + @Override + protected String buildPreparedOldValueCondition(String quotedSourceColumn) { + return this.buildPreparedNullableEqualityCondition(quotedSourceColumn); + } + + @Override + protected String buildAuditOldValueCondition(String quotedSourceColumn, String renderedOldValue) { + return this.buildAuditNullableEqualityCondition(quotedSourceColumn, renderedOldValue); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialect.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialect.java new file mode 100644 index 0000000..aeb25a8 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialect.java @@ -0,0 +1,53 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import org.jumpserver.chen.framework.datasource.entity.resource.Field; + +import java.sql.SQLException; + +public interface TableEditDialect { + String quoteIdentifier(String identifier); + + String buildPreparedUpdateSql(String schema, String table, String sourceColumn, String pkColumn); + + String buildPreparedDeleteSql(String schema, String table, String pkColumn); + + String buildPreparedInsertSql(String schema, String table, java.util.List sourceColumns); + + int oldValueParameterCount(); + + // For preview/audit SQL only. Real writes must use PreparedStatement binding. + String renderLiteral(Object value, boolean isNull, Field field) throws SQLException; + + String buildAuditUpdateSql( + String schema, + String table, + String sourceColumn, + Field targetField, + Object newValue, + boolean newValueIsNull, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull, + Object oldValue, + boolean oldValueIsNull + ) throws SQLException; + + String buildAuditDeleteSql( + String schema, + String table, + String pkColumn, + Field pkField, + Object pkValue, + boolean pkValueIsNull + ) throws SQLException; + + String buildAuditInsertSql( + String schema, + String table, + java.util.List sourceColumns, + java.util.List fields, + java.util.List values, + java.util.List valueIsNulls + ) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialects.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialects.java new file mode 100644 index 0000000..34f2fee --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/dialect/TableEditDialects.java @@ -0,0 +1,39 @@ +package org.jumpserver.chen.framework.datasource.edit.dialect; + +import com.alibaba.druid.DbType; + +import java.util.Optional; + +public final class TableEditDialects { + private static final TableEditDialect POSTGRESQL = new PostgresqlTableEditDialect(); + private static final TableEditDialect MYSQL = new MysqlTableEditDialect(); + private static final TableEditDialect ORACLE = new OracleTableEditDialect(); + private static final TableEditDialect SQLSERVER = new SqlServerTableEditDialect(); + private static final TableEditDialect DAMENG = new DamengTableEditDialect(); + private static final TableEditDialect DB2 = new Db2TableEditDialect(); + + private TableEditDialects() { + } + + public static Optional find(DbType dbType) { + if (dbType == DbType.postgresql) { + return Optional.of(POSTGRESQL); + } + if (dbType == DbType.mysql || dbType == DbType.mariadb) { + return Optional.of(MYSQL); + } + if (dbType == DbType.oracle) { + return Optional.of(ORACLE); + } + if (dbType == DbType.sqlserver) { + return Optional.of(SQLSERVER); + } + if (dbType == DbType.dm) { + return Optional.of(DAMENG); + } + if (dbType == DbType.db2) { + return Optional.of(DB2); + } + return Optional.empty(); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/CommitOutcomeUnknownException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/CommitOutcomeUnknownException.java new file mode 100644 index 0000000..98efae5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/CommitOutcomeUnknownException.java @@ -0,0 +1,11 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class CommitOutcomeUnknownException extends TableEditException { + public CommitOutcomeUnknownException(SQLException cause) { + super(TableChangesSaveService.SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN, cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/OptimisticLockConflictException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/OptimisticLockConflictException.java new file mode 100644 index 0000000..51a4a25 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/OptimisticLockConflictException.java @@ -0,0 +1,16 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +public class OptimisticLockConflictException extends TableEditException { + private final int changeIndex; + + public OptimisticLockConflictException(int changeIndex) { + super(TableChangesSaveService.OPTIMISTIC_LOCK_CONFLICT); + this.changeIndex = changeIndex; + } + + public int getChangeIndex() { + return changeIndex; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RollbackFailedException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RollbackFailedException.java new file mode 100644 index 0000000..5685fe4 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RollbackFailedException.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class RollbackFailedException extends TableEditException { + public RollbackFailedException(SQLException cause, Throwable primaryException) { + super(TableChangesSaveService.SAVE_CHANGES_ROLLBACK_FAILED, cause); + this.addSuppressed(primaryException); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RolledBackConnectionUnavailableException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RolledBackConnectionUnavailableException.java new file mode 100644 index 0000000..1f5c376 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RolledBackConnectionUnavailableException.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class RolledBackConnectionUnavailableException extends TableEditException { + public RolledBackConnectionUnavailableException(SQLException cause, Throwable primaryException) { + super(TableChangesSaveService.SAVE_CHANGES_ROLLED_BACK_CONNECTION_UNAVAILABLE, cause); + this.addSuppressed(primaryException); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RowNotFoundOrNotUniqueException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RowNotFoundOrNotUniqueException.java new file mode 100644 index 0000000..022dc8b --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/RowNotFoundOrNotUniqueException.java @@ -0,0 +1,18 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class RowNotFoundOrNotUniqueException extends SQLException { + private final int changeIndex; + + public RowNotFoundOrNotUniqueException(int changeIndex) { + super(TableChangesSaveService.ROW_NOT_FOUND_OR_NOT_UNIQUE); + this.changeIndex = changeIndex; + } + + public int getChangeIndex() { + return changeIndex; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/SavepointRollbackFailedException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/SavepointRollbackFailedException.java new file mode 100644 index 0000000..286b6eb --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/SavepointRollbackFailedException.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +import java.sql.SQLException; + +public class SavepointRollbackFailedException extends TableEditException { + public SavepointRollbackFailedException(SQLException cause, Throwable primaryException) { + super(TableChangesSaveService.SAVE_CHANGES_SAVEPOINT_ROLLBACK_FAILED, cause); + this.addSuppressed(primaryException); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/TableEditException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/TableEditException.java new file mode 100644 index 0000000..1288e9e --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/TableEditException.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import java.sql.SQLException; + +public class TableEditException extends SQLException { + public TableEditException(String reason) { + super(reason); + } + + public TableEditException(String reason, Throwable cause) { + super(reason, cause); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/UnexpectedAffectedRowsException.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/UnexpectedAffectedRowsException.java new file mode 100644 index 0000000..a099ae3 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/exception/UnexpectedAffectedRowsException.java @@ -0,0 +1,22 @@ +package org.jumpserver.chen.framework.datasource.edit.exception; + +import org.jumpserver.chen.framework.datasource.edit.TableChangesSaveService; + +public class UnexpectedAffectedRowsException extends TableEditException { + private final int changeIndex; + private final int affectedRows; + + public UnexpectedAffectedRowsException(int changeIndex, int affectedRows) { + super(TableChangesSaveService.AFFECTED_ROWS_UNEXPECTED); + this.changeIndex = changeIndex; + this.affectedRows = affectedRows; + } + + public int getChangeIndex() { + return changeIndex; + } + + public int getAffectedRows() { + return affectedRows; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/JdbcPrimaryKeyResolver.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/JdbcPrimaryKeyResolver.java new file mode 100644 index 0000000..83d3be5 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/JdbcPrimaryKeyResolver.java @@ -0,0 +1,252 @@ +package org.jumpserver.chen.framework.datasource.edit.pk; + +import com.alibaba.druid.DbType; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.edit.analyzer.EditabilityReason; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class JdbcPrimaryKeyResolver implements PrimaryKeyResolver { + private final Connection connection; + private final DbType dbType; + + public JdbcPrimaryKeyResolver(Connection connection, DbType dbType) { + this.connection = connection; + this.dbType = dbType; + } + + @Override + public PrimaryKeyResolution resolvePrimaryKeys(String schema, String table) throws SQLException { + return switch (this.dbType) { + case postgresql -> this.resolvePostgresqlPrimaryKeysWithTableCheck(schema, table); + case mysql, mariadb -> this.resolveMysqlPrimaryKeysWithTableCheck(schema, table); + case oracle -> this.resolveOraclePrimaryKeysWithTableCheck(schema, table); + case sqlserver -> this.resolveSqlServerPrimaryKeysWithTableCheck(schema, table); + case dm -> this.resolveDamengPrimaryKeysWithTableCheck(schema, table); + case db2 -> this.resolveDb2PrimaryKeysWithTableCheck(schema, table); + default -> null; + }; + } + + private PrimaryKeyResolution resolvePostgresqlPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isPostgresqlView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolvePostgresqlPrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveMysqlPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isMysqlView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveMysqlPrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveOraclePrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isOracleView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveOraclePrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveSqlServerPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isSqlServerView(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveSqlServerPrimaryKeys(schema, table)); + } + + private PrimaryKeyResolution resolveDamengPrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + var metadata = this.connection.getMetaData(); + String schemaPattern = StringUtils.trimToNull(schema); + try (var resultSet = metadata.getTables(null, schemaPattern, table, new String[]{"VIEW"})) { + if (resultSet.next()) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + } + + List primaryKeys = new ArrayList<>(); + try (var resultSet = metadata.getPrimaryKeys(null, schemaPattern, table)) { + while (resultSet.next()) { + primaryKeys.add(resultSet.getString("COLUMN_NAME")); + } + } + return PrimaryKeyResolution.primaryKeys(primaryKeys); + } + + private PrimaryKeyResolution resolveDb2PrimaryKeysWithTableCheck(String schema, String table) throws SQLException { + if (this.isDb2View(schema, table)) { + return PrimaryKeyResolution.readOnly(EditabilityReason.VIEW_NOT_SUPPORTED); + } + return PrimaryKeyResolution.primaryKeys(this.resolveDb2PrimaryKeys(schema, table)); + } + + private boolean isPostgresqlView(String schema, String table) throws SQLException { + String sql = """ + SELECT table_type + FROM information_schema.tables + WHERE table_schema = COALESCE(?, current_schema()) + AND table_name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isMysqlView(String schema, String table) throws SQLException { + String sql = """ + SELECT table_type + FROM information_schema.tables + WHERE table_schema = COALESCE(?, DATABASE()) + AND table_name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isOracleView(String schema, String table) throws SQLException { + String sql = """ + SELECT 'VIEW' + FROM all_views + WHERE owner = COALESCE(?, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')) + AND view_name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isSqlServerView(String schema, String table) throws SQLException { + String sql = """ + SELECT 'VIEW' + FROM sys.views v + JOIN sys.schemas s + ON v.schema_id = s.schema_id + WHERE s.name = COALESCE(?, 'dbo') + AND v.name = ? + """; + return this.isView(sql, schema, table); + } + + private boolean isDb2View(String schema, String table) throws SQLException { + String sql = """ + SELECT 'VIEW' + FROM syscat.tables + WHERE tabschema = COALESCE(?, CURRENT SCHEMA) + AND tabname = ? + AND type = 'V' + """; + return this.isView(sql, schema, table); + } + + private boolean isView(String sql, String schema, String table) throws SQLException { + try (var statement = this.connection.prepareStatement(sql)) { + bindSchemaAndTable(statement, schema, table); + try (var resultSet = statement.executeQuery()) { + return resultSet.next() && StringUtils.equalsIgnoreCase(resultSet.getString(1), "VIEW"); + } + } + } + + private List resolvePostgresqlPrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT kcu.column_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + AND tc.table_name = kcu.table_name + WHERE tc.constraint_type = 'PRIMARY KEY' + AND tc.table_schema = COALESCE(?, current_schema()) + AND tc.table_name = ? + ORDER BY kcu.ordinal_position + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveMysqlPrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT column_name + FROM information_schema.key_column_usage + WHERE constraint_name = 'PRIMARY' + AND table_schema = COALESCE(?, DATABASE()) + AND table_name = ? + ORDER BY ordinal_position + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveOraclePrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT acc.column_name + FROM all_constraints ac + JOIN all_cons_columns acc + ON ac.owner = acc.owner + AND ac.constraint_name = acc.constraint_name + AND ac.table_name = acc.table_name + WHERE ac.constraint_type = 'P' + AND ac.owner = COALESCE(?, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')) + AND ac.table_name = ? + ORDER BY acc.position + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveSqlServerPrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT c.name + FROM sys.key_constraints kc + JOIN sys.index_columns ic + ON kc.parent_object_id = ic.object_id + AND kc.unique_index_id = ic.index_id + JOIN sys.columns c + ON ic.object_id = c.object_id + AND ic.column_id = c.column_id + JOIN sys.tables t + ON kc.parent_object_id = t.object_id + JOIN sys.schemas s + ON t.schema_id = s.schema_id + WHERE kc.type = 'PK' + AND s.name = COALESCE(?, 'dbo') + AND t.name = ? + ORDER BY ic.key_ordinal + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List resolveDb2PrimaryKeys(String schema, String table) throws SQLException { + String sql = """ + SELECT kcu.colname + FROM syscat.tabconst tc + JOIN syscat.keycoluse kcu + ON tc.constname = kcu.constname + AND tc.tabschema = kcu.tabschema + AND tc.tabname = kcu.tabname + WHERE tc.type = 'P' + AND tc.tabschema = COALESCE(?, CURRENT SCHEMA) + AND tc.tabname = ? + ORDER BY kcu.colseq + """; + return this.queryPrimaryKeys(sql, schema, table); + } + + private List queryPrimaryKeys(String sql, String schema, String table) throws SQLException { + List primaryKeys = new ArrayList<>(); + try (var statement = this.connection.prepareStatement(sql)) { + bindSchemaAndTable(statement, schema, table); + try (var resultSet = statement.executeQuery()) { + while (resultSet.next()) { + primaryKeys.add(resultSet.getString(1)); + } + } + } + return primaryKeys; + } + + private static void bindSchemaAndTable(java.sql.PreparedStatement statement, String schema, String table) throws SQLException { + if (StringUtils.isBlank(schema)) { + statement.setNull(1, java.sql.Types.VARCHAR); + } else { + statement.setString(1, schema); + } + statement.setString(2, table); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolution.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolution.java new file mode 100644 index 0000000..13e3e14 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolution.java @@ -0,0 +1,24 @@ +package org.jumpserver.chen.framework.datasource.edit.pk; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class PrimaryKeyResolution { + private final List primaryKeys; + private final String readOnlyReason; + + private PrimaryKeyResolution(List primaryKeys, String readOnlyReason) { + this.primaryKeys = primaryKeys; + this.readOnlyReason = readOnlyReason; + } + + public static PrimaryKeyResolution primaryKeys(List primaryKeys) { + return new PrimaryKeyResolution(primaryKeys, null); + } + + public static PrimaryKeyResolution readOnly(String reason) { + return new PrimaryKeyResolution(List.of(), reason); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolver.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolver.java new file mode 100644 index 0000000..f36634c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/edit/pk/PrimaryKeyResolver.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.datasource.edit.pk; + +import java.sql.SQLException; + +@FunctionalInterface +public interface PrimaryKeyResolver { + PrimaryKeyResolution resolvePrimaryKeys(String schema, String table) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java index 34d3ba4..6d93f0b 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/Field.java @@ -12,8 +12,21 @@ public class Field implements ResourceNode { private String schema; private String table; private String type; + private transient Integer jdbcType; + private String sourceSchema; + private String sourceTable; + private String sourceColumn; private boolean nullable; + private boolean editable; + private String editReason; private boolean isPrimaryKey; + private boolean masked; + private boolean autoIncrement; + private boolean readOnly; + private transient boolean generated; + private boolean insertable; + private boolean requiredOnInsert; + private String insertReason; public void setNullable(String nullable) { String[] trueAlias = {"YES", "Y"}; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/ResourceNodeSnapshot.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/ResourceNodeSnapshot.java new file mode 100644 index 0000000..0e57f4a --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/entity/resource/ResourceNodeSnapshot.java @@ -0,0 +1,11 @@ +package org.jumpserver.chen.framework.datasource.entity.resource; + +public record ResourceNodeSnapshot( + String key, + String type, + String database, + String schema, + String table, + String name +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/hints/SQLHintsHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/hints/SQLHintsHandler.java deleted file mode 100644 index f9efa2d..0000000 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/hints/SQLHintsHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.jumpserver.chen.framework.datasource.hints; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public interface SQLHintsHandler { - Map> getHints(String nodeKey, String context) throws SQLException; -} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/BaseDatabaseMetadataProvider.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/BaseDatabaseMetadataProvider.java new file mode 100644 index 0000000..4ed4885 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/BaseDatabaseMetadataProvider.java @@ -0,0 +1,297 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; + +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Shared plumbing for dialect providers: the parameterized query entry point, + * dialect identifier quoting, IN-list chunking, and canonical row-mapping helpers. + */ +public abstract class BaseDatabaseMetadataProvider implements DatabaseMetadataProvider { + + protected final ConnectionManager connectionManager; + + protected BaseDatabaseMetadataProvider(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + } + + /** + * The only SQL execution primitive providers should use. All identifiers in + * value position must be passed as {@code ?} parameters; identifiers in + * identifier position must be {@link #quoteIdentifier(String)}-quoted and + * already canonical-validated by the caller. + */ + protected List> query(String sql, List parameters) throws SQLException { + return this.connectionManager.getSqlActuator().queryRows(sql, parameters); + } + + /** + * Dialect-specific identifier quoting. Default is SQL-standard double quotes. + * Override for dialects that use a different quote (e.g. backticks). + */ + protected String quoteIdentifier(String value) { + return "\"" + value.replace("\"", "\"\"") + "\""; + } + + /** Split a name list into sub-batches that respect an IN-clause bound. */ + protected static List> chunk(List values, int bound) { + var chunks = new ArrayList>(); + for (int i = 0; i < values.size(); i += bound) { + chunks.add(values.subList(i, Math.min(values.size(), i + bound))); + } + return chunks; + } + + // -- shared row mapping ------------------------------------------------- + + protected static String stringValue(Map row, String key) { + var value = row.get(key); + return value == null ? null : String.valueOf(value); + } + + protected static Long longValue(Map row, String key) { + var value = row.get(key); + if (value instanceof Number number) { + return number.longValue(); + } + if (value == null || String.valueOf(value).isBlank()) { + return null; + } + try { + return new BigDecimal(String.valueOf(value)).longValue(); + } catch (NumberFormatException e) { + return null; + } + } + + protected static Boolean booleanValue(Map row, String key) { + var value = row.get(key); + if (value instanceof Boolean bool) { + return bool; + } + if (value instanceof Number number) { + return number.intValue() != 0; + } + if (value == null) { + return null; + } + return switch (String.valueOf(value).toLowerCase(Locale.ROOT)) { + case "true", "t", "yes", "y", "1" -> true; + case "false", "f", "no", "n", "0" -> false; + default -> null; + }; + } + + /** + * Groups flat index rows (one row per column) into {@link IndexMetadata}, + * preserving column order and folding the shared definition onto the index. + */ + protected List groupIndexRows(List> rows, RelationScope scope) { + var grouped = new LinkedHashMap(); + for (var row : rows) { + var name = stringValue(row, "name"); + var table = stringValue(row, "table_name"); + var key = table + "\u0000" + name; + var index = grouped.computeIfAbsent(key, ignored -> new MutableIndex( + name, + table, + booleanValue(row, "is_unique"), + stringValue(row, "method"), + stringValue(row, "definition") + )); + var column = stringValue(row, "column_name"); + var expression = stringValue(row, "expression"); + if (column != null && !column.isBlank()) { + var included = Boolean.TRUE.equals(booleanValue(row, "included")); + index.parts.add(new IndexPart(index.parts.size(), column, null, null, included)); + } else if (expression != null && !expression.isBlank()) { + index.parts.add(new IndexPart(index.parts.size(), null, expression, null, false)); + } + if (index.definition == null) { + index.definition = stringValue(row, "definition"); + } + } + var result = new ArrayList(grouped.size()); + for (var index : grouped.values()) { + result.add(new IndexMetadata( + index.name, + new ObjectRef(scope.catalog(), scope.schema(), index.table, RelationKind.TABLE), + Boolean.TRUE.equals(index.unique), + index.method, + List.copyOf(index.parts), + index.definition + )); + } + return result; + } + + /** + * Shared batched column load. Groups relations by schema, chunks the name + * list to respect IN-clause limits, and reassociates each column with its + * owner via the {@code table_name} projection in {@code sqlTemplate}. + * + *

{@code sqlTemplate} must contain a {@code __IN__} marker where the + * (comma-separated) {@code ?} placeholders are inserted, and project the + * canonical aliases {@code name}/{@code table_name}/{@code native_type}/ + * {@code nullable}.

+ */ + protected List loadColumns(String sqlTemplate, List relations) throws SQLException { + if (relations.isEmpty()) { + return List.of(); + } + var result = new ArrayList(); + var bySchema = new LinkedHashMap>(); + for (var relation : relations) { + bySchema.computeIfAbsent(relation.schema(), ignored -> new ArrayList<>()).add(relation); + } + for (var entry : bySchema.entrySet()) { + var schema = entry.getKey(); + var refs = entry.getValue(); + var refByName = new HashMap(); + for (var ref : refs) { + refByName.put(ref.name(), ref); + } + var names = refs.stream().map(ObjectRef::name).toList(); + for (var batch : chunk(names, 500)) { + var placeholders = String.join(",", Collections.nCopies(batch.size(), "?")); + var params = new ArrayList(); + params.add(schema); + params.addAll(batch); + var sql = sqlTemplate.replace("__IN__", placeholders); + for (var row : query(sql, params)) { + var owner = refByName.get(stringValue(row, "table_name")); + if (owner == null) { + continue; + } + result.add(new ColumnMetadata( + owner, + stringValue(row, "name"), + 0, + stringValue(row, "native_type"), + 0, + Boolean.TRUE.equals(booleanValue(row, "nullable")), + null, + null + )); + } + } + } + return result; + } + + // -- defaults ----------------------------------------------------------- + + @Override + public List listCatalogs() throws SQLException { + return List.of(); + } + + @Override + public List listSchemas(String catalog) throws SQLException { + return List.of(); + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + throw unsupported("relations"); + } + + @Override + public List searchRelations(RelationScope scope, Set kinds, String prefix, int limit) + throws SQLException { + var relations = this.listRelations(scope, kinds); + var normalizedPrefix = prefix == null ? "" : prefix.trim().toLowerCase(Locale.ROOT); + var stream = relations.stream() + .filter(relation -> normalizedPrefix.isEmpty() + || relation.ref().name().toLowerCase(Locale.ROOT).startsWith(normalizedPrefix) + || (relation.ref().schema() + "." + relation.ref().name()) + .toLowerCase(Locale.ROOT).startsWith(normalizedPrefix)); + if (limit > 0) { + stream = stream.limit(limit); + } + return stream.toList(); + } + + @Override + public List listColumns(List relations) throws SQLException { + throw unsupported("columns"); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + throw unsupported("indexes"); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + throw unsupported("statistics"); + } + + @Override + public List listPrimaryKeys(List relations) throws SQLException { + throw unsupported("primary keys"); + } + + @Override + public List listForeignKeys(List relations) throws SQLException { + throw unsupported("foreign keys"); + } + + @Override + public String getSchemaDefinition(RelationScope scope) throws SQLException { + return null; + } + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return new ObjectProperties(ref, List.of()); + } + + /** + * Runs a single-row vendor properties query and maps its first row to + * {@link PropertyItem}s in column order. + */ + protected ObjectProperties loadObjectProperties(ObjectRef ref, String sql) throws SQLException { + var rows = query(sql, List.of(ref.schema(), ref.name())); + if (rows.isEmpty()) { + return new ObjectProperties(ref, List.of()); + } + var items = new ArrayList(); + for (var entry : rows.get(0).entrySet()) { + var value = entry.getValue(); + items.add(new PropertyItem(entry.getKey(), value == null ? null : String.valueOf(value))); + } + return new ObjectProperties(ref, items); + } + + private static UnsupportedOperationException unsupported(String category) { + return new UnsupportedOperationException("Metadata category not supported: " + category); + } + + private static final class MutableIndex { + private final String name; + private final String table; + private final Boolean unique; + private final String method; + private String definition; + private final List parts = new ArrayList<>(); + + private MutableIndex(String name, String table, Boolean unique, String method, String definition) { + this.name = name; + this.table = table; + this.unique = unique; + this.method = method; + this.definition = definition; + } + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/CatalogMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/CatalogMetadata.java new file mode 100644 index 0000000..c9aa248 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/CatalogMetadata.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public record CatalogMetadata(String name) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ColumnMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ColumnMetadata.java new file mode 100644 index 0000000..f30e61c --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ColumnMetadata.java @@ -0,0 +1,18 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Canonical column metadata. {@code owner} carries the owning relation so batch + * results can be reassociated. {@code nativeType} is the SQL type name; + * {@code jdbcType} is the {@link java.sql.Types} code (0 when unknown). + */ +public record ColumnMetadata( + ObjectRef owner, + String name, + int ordinal, + String nativeType, + int jdbcType, + boolean nullable, + String defaultValue, + String comment +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/DatabaseMetadataProvider.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/DatabaseMetadataProvider.java new file mode 100644 index 0000000..1a2de39 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/DatabaseMetadataProvider.java @@ -0,0 +1,59 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +/** + * Canonical metadata source for one SQL dialect. Providers are stateless with + * respect to cache; they hold a {@link org.jumpserver.chen.framework.datasource.ConnectionManager} + * reference and issue parameterized metadata queries. + * + *

The catalog never calls a category whose {@link #capabilities()} flag is + * {@code false}. Unsupported categories therefore either return an empty/natural + * default (containers, schema DDL, vendor properties) or throw + * {@link UnsupportedOperationException} from the base implementation.

+ */ +public interface DatabaseMetadataProvider { + + MetadataCapabilities capabilities(); + + List listCatalogs() throws SQLException; + + List listSchemas(String catalog) throws SQLException; + + /** + * Full relation list for a scope. Cacheable; {@code kinds} restricts which + * relation kinds are returned. + */ + List listRelations(RelationScope scope, Set kinds) throws SQLException; + + /** + * Completion-oriented search. Prefix and limit are applied by the caller + * (base default filters in memory); a dialect may override to push the + * predicate down. Not cached by the catalog. + */ + List searchRelations(RelationScope scope, Set kinds, String prefix, int limit) + throws SQLException; + + /** + * Batched column load. Every returned {@link ColumnMetadata} carries its + * owning {@link ObjectRef} so the caller can reassociate batch results. + */ + List listColumns(List relations) throws SQLException; + + List listIndexes(RelationScope scope) throws SQLException; + + List listStatistics(RelationScope scope) throws SQLException; + + List listPrimaryKeys(List relations) throws SQLException; + + List listForeignKeys(List relations) throws SQLException; + + /** + * Schema/database DDL, or {@code null} when the dialect does not provide one. + */ + String getSchemaDefinition(RelationScope scope) throws SQLException; + + ObjectProperties objectProperties(ObjectRef ref) throws SQLException; +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ForeignKeyMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ForeignKeyMetadata.java new file mode 100644 index 0000000..59339f1 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ForeignKeyMetadata.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +public record ForeignKeyMetadata( + ObjectRef owner, + String name, + List columns, + ObjectRef referenced, + List referencedColumns +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexMetadata.java new file mode 100644 index 0000000..0fc2a93 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexMetadata.java @@ -0,0 +1,13 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +public record IndexMetadata( + String name, + ObjectRef owner, + boolean unique, + String method, + List parts, + String definition +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexPart.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexPart.java new file mode 100644 index 0000000..dd91497 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/IndexPart.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * One column or expression participating in an index. + * Exactly one of {@code columnName} / {@code expression} is non-null. + */ +public record IndexPart( + int ordinal, + String columnName, + String expression, + String sortOrder, + boolean included +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCapabilities.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCapabilities.java new file mode 100644 index 0000000..eb002cc --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCapabilities.java @@ -0,0 +1,26 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Per-dialect capability flags. Coarse category flags drive whether the catalog + * may call a provider method; fine field-level flags drive which relation/ + * statistics columns a consumer (e.g. schema overview) may render. + */ +public record MetadataCapabilities( + boolean catalogs, + boolean schemas, + boolean relations, + boolean columns, + boolean indexes, + boolean primaryKeys, + boolean foreignKeys, + boolean statistics, + boolean definitions, + boolean tableRows, + boolean tableSize, + boolean tableEngine, + boolean tableCharacterSet, + boolean tableCollation, + boolean tableComment, + boolean viewComment +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCatalog.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCatalog.java new file mode 100644 index 0000000..03ac711 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/MetadataCatalog.java @@ -0,0 +1,287 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Session-scoped metadata facade: cache + invalidation + database-context + * switching over a {@link DatabaseMetadataProvider}. It manages metadata only, + * not the tree children cache, and does not depend on {@code ResourceBrowser}. + * + *

Object-scoped categories (columns, keys) are cached per relation and batch + * the misses into a single provider call; schema-scoped categories (relations, + * indexes, statistics, definitions) are cached per scope.

+ */ +public class MetadataCatalog { + + private static final long DEFAULT_TTL_MILLIS = 30 * 60 * 1000L; + private static final long STATISTICS_TTL_MILLIS = 5 * 60 * 1000L; + + private static final Set ALL_KINDS = EnumSet.allOf(RelationKind.class); + + private final ConnectionManager connectionManager; + private final DatabaseMetadataProvider provider; + private final Map cache = new ConcurrentHashMap<>(); + + public MetadataCatalog(ConnectionManager connectionManager, DatabaseMetadataProvider provider) { + this.connectionManager = Objects.requireNonNull(connectionManager); + this.provider = Objects.requireNonNull(provider); + } + + public MetadataCapabilities capabilities() { + return this.provider.capabilities(); + } + + // -- containers --------------------------------------------------------- + + public List listCatalogs() throws SQLException { + var key = new CacheKey(null, null, null, Category.CATALOGS); + return cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().catalogs(), () -> + provider.listCatalogs()); + } + + public List listSchemas(String catalog) throws SQLException { + var key = new CacheKey(catalog, null, null, Category.SCHEMAS); + return cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().schemas(), () -> + connectionManager.withDatabaseContext(catalog, () -> provider.listSchemas(catalog))); + } + + // -- relations ---------------------------------------------------------- + + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.RELATIONS); + var all = cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().relations(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.listRelations(scope, ALL_KINDS))); + if (all.isEmpty()) { + return List.of(); + } + return all.stream().filter(relation -> kinds.contains(relation.ref().kind())).toList(); + } + + public List searchRelations(RelationScope scope, Set kinds, String prefix, int limit) + throws SQLException { + if (!provider.capabilities().relations()) { + return List.of(); + } + return connectionManager.withDatabaseContext(scope.catalog(), () -> + provider.searchRelations(scope, kinds, prefix, limit)); + } + + // -- columns / keys (object-scoped, batch of misses) -------------------- + + public List listColumns(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().columns()) { + return List.of(); + } + return loadObjectScoped(relations, Category.COLUMNS, provider::listColumns, DEFAULT_TTL_MILLIS); + } + + public List listPrimaryKeys(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().primaryKeys()) { + return List.of(); + } + return loadObjectScoped(relations, Category.PRIMARY_KEYS, provider::listPrimaryKeys, DEFAULT_TTL_MILLIS); + } + + public List listForeignKeys(List relations) throws SQLException { + if (relations.isEmpty() || !provider.capabilities().foreignKeys()) { + return List.of(); + } + return loadObjectScoped(relations, Category.FOREIGN_KEYS, provider::listForeignKeys, DEFAULT_TTL_MILLIS); + } + + // -- schema-scoped ------------------------------------------------------ + + public List listIndexes(RelationScope scope) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.INDEXES); + return cachedList(key, DEFAULT_TTL_MILLIS, provider.capabilities().indexes(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.listIndexes(scope))); + } + + public List listStatistics(RelationScope scope) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.STATISTICS); + return cachedList(key, STATISTICS_TTL_MILLIS, provider.capabilities().statistics(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.listStatistics(scope))); + } + + public String getSchemaDefinition(RelationScope scope) throws SQLException { + var key = new CacheKey(scope.catalog(), scope.schema(), null, Category.DEFINITIONS); + return cachedNullable(key, DEFAULT_TTL_MILLIS, provider.capabilities().definitions(), () -> + connectionManager.withDatabaseContext(scope.catalog(), () -> provider.getSchemaDefinition(scope))); + } + + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + if (!provider.capabilities().relations()) { + return new ObjectProperties(ref, List.of()); + } + return connectionManager.withDatabaseContext(ref.catalog(), () -> provider.objectProperties(ref)); + } + + // -- invalidation ------------------------------------------------------- + + public void invalidate(RelationScope scope) { + invalidatePrefix(scope.catalog(), scope.schema()); + } + + public void invalidate(ObjectRef ref) { + invalidatePrefix(ref.catalog(), ref.schema(), ref.name()); + } + + public void invalidateAll() { + cache.clear(); + } + + // -- internals ---------------------------------------------------------- + + private List cachedList(CacheKey key, long ttlMillis, boolean supported, Loader> loader) + throws SQLException { + return cached(key, ttlMillis, supported, loader, List.of()); + } + + private T cachedNullable(CacheKey key, long ttlMillis, boolean supported, Loader loader) + throws SQLException { + return cached(key, ttlMillis, supported, loader, null); + } + + private T cached(CacheKey key, long ttlMillis, boolean supported, Loader loader, T emptyValue) + throws SQLException { + var now = System.currentTimeMillis(); + var existing = cache.get(key); + if (existing != null && now - existing.loadedAt() < ttlMillis) { + if (existing.state() == State.UNSUPPORTED) { + return emptyValue; + } + return cast(existing.value()); + } + if (!supported) { + cache.put(key, new CacheEntry(State.UNSUPPORTED, null, now)); + return emptyValue; + } + var value = loader.load(); + var state = isEmpty(value) ? State.LOADED_EMPTY : State.LOADED; + cache.put(key, new CacheEntry(state, value, now)); + return value; + } + + private List loadObjectScoped(List relations, Category category, ObjectLoader loader, long ttlMillis) + throws SQLException { + var result = new ArrayList(); + var misses = new ArrayList(); + var now = System.currentTimeMillis(); + for (var ref : relations) { + var key = new CacheKey(ref.catalog(), ref.schema(), ref.name(), category); + var existing = cache.get(key); + if (existing != null && existing.state() != State.UNSUPPORTED && now - existing.loadedAt() < ttlMillis) { + result.addAll(castList(existing.value())); + } else { + misses.add(ref); + } + } + if (misses.isEmpty()) { + return result; + } + + var missesByScope = new LinkedHashMap>(); + for (var ref : misses) { + missesByScope.computeIfAbsent(new RelationScope(ref.catalog(), ref.schema()), ignored -> new ArrayList<>()).add(ref); + } + + for (var entry : missesByScope.entrySet()) { + var scope = entry.getKey(); + var scopeRefs = entry.getValue(); + var fetched = connectionManager.withDatabaseContext(scope.catalog(), () -> loader.load(scopeRefs)); + var byOwner = new LinkedHashMap>(); + for (var item : fetched) { + var owner = ownerOf(item); + if (owner != null) { + byOwner.computeIfAbsent(owner, ignored -> new ArrayList<>()).add(item); + } + } + for (var ref : scopeRefs) { + var items = byOwner.getOrDefault(ref, List.of()); + var key = new CacheKey(ref.catalog(), ref.schema(), ref.name(), category); + cache.put(key, new CacheEntry( + items.isEmpty() ? State.LOADED_EMPTY : State.LOADED, + items, + System.currentTimeMillis() + )); + result.addAll(items); + } + } + return result; + } + + @SuppressWarnings("unchecked") + private static ObjectRef ownerOf(T item) { + if (item instanceof ColumnMetadata column) { + return column.owner(); + } + if (item instanceof PrimaryKeyMetadata pk) { + return pk.owner(); + } + if (item instanceof ForeignKeyMetadata fk) { + return fk.owner(); + } + return null; + } + + private static boolean isEmpty(Object value) { + return value instanceof Collection collection && collection.isEmpty(); + } + + private void invalidatePrefix(String catalog, String schema) { + invalidatePrefix(catalog, schema, null); + } + + private void invalidatePrefix(String catalog, String schema, String object) { + cache.keySet().removeIf(key -> + (object == null || key.object() == null || key.object().equals(object)) + && Objects.equals(key.catalog(), catalog) + && Objects.equals(key.schema(), schema)); + } + + @SuppressWarnings("unchecked") + private static T cast(Object value) { + return (T) value; + } + + @SuppressWarnings("unchecked") + private static List castList(Object value) { + return value instanceof List list ? (List) list : List.of(); + } + + private enum Category { + CATALOGS, SCHEMAS, RELATIONS, COLUMNS, INDEXES, STATISTICS, + PRIMARY_KEYS, FOREIGN_KEYS, DEFINITIONS + } + + private enum State { + LOADED, LOADED_EMPTY, UNSUPPORTED + } + + private record CacheKey(String catalog, String schema, String object, Category category) { + } + + private record CacheEntry(State state, Object value, long loadedAt) { + } + + @FunctionalInterface + private interface Loader { + T load() throws SQLException; + } + + @FunctionalInterface + private interface ObjectLoader { + List load(List relations) throws SQLException; + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectProperties.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectProperties.java new file mode 100644 index 0000000..7bfcea9 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectProperties.java @@ -0,0 +1,10 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +/** + * Generic vendor-specific properties not representable by the canonical model. + * Order-preserving; used by Show Properties to avoid silently dropping fields. + */ +public record ObjectProperties(ObjectRef ref, List items) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectRef.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectRef.java new file mode 100644 index 0000000..0150959 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectRef.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Canonical reference to a relation (or, by extension, a schema/catalog object). + * JSON-compatible with the previous {@code QualifiedRelation} shape + * (catalog is nullable, kind is the lowercase {@link RelationKind#code()}). + */ +public record ObjectRef( + String catalog, + String schema, + String name, + RelationKind kind +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectStatistics.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectStatistics.java new file mode 100644 index 0000000..c984ed6 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/ObjectStatistics.java @@ -0,0 +1,8 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Estimated row count and total storage size for a relation. Estimates only; + * never produced by {@code COUNT(*)}. + */ +public record ObjectStatistics(ObjectRef ref, Long estimatedRows, Long totalSizeBytes) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PrimaryKeyMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PrimaryKeyMetadata.java new file mode 100644 index 0000000..ab0c4f0 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PrimaryKeyMetadata.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +import java.util.List; + +public record PrimaryKeyMetadata(ObjectRef owner, String name, List columns) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PropertyItem.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PropertyItem.java new file mode 100644 index 0000000..972af5f --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/PropertyItem.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public record PropertyItem(String name, String value) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationKind.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationKind.java new file mode 100644 index 0000000..83ec960 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationKind.java @@ -0,0 +1,31 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Canonical relation kind. The {@link #code()} value is the wire form used in + * {@link ObjectRef}; keep it stable so it stays JSON-compatible with the + * completion client contract ("table" / "view"). + */ +public enum RelationKind { + TABLE("table"), + VIEW("view"), + MATERIALIZED_VIEW("materialized_view"); + + private final String code; + + RelationKind(String code) { + this.code = code; + } + + public String code() { + return this.code; + } + + public static RelationKind fromCode(String code) { + for (var kind : values()) { + if (kind.code.equals(code)) { + return kind; + } + } + throw new IllegalArgumentException("Unknown relation kind: " + code); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationMetadata.java new file mode 100644 index 0000000..17425ba --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationMetadata.java @@ -0,0 +1,14 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * Structural metadata about a relation, excluding statistics (rows/size), which + * have a different refresh frequency and live in {@link ObjectStatistics}. + */ +public record RelationMetadata( + ObjectRef ref, + String comment, + String engine, + String characterSet, + String collation +) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationScope.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationScope.java new file mode 100644 index 0000000..fe07348 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/RelationScope.java @@ -0,0 +1,7 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +/** + * The (catalog, schema) context for schema-scoped batch queries. + */ +public record RelationScope(String catalog, String schema) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/SchemaMetadata.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/SchemaMetadata.java new file mode 100644 index 0000000..9d4c908 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/metadata/SchemaMetadata.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.framework.datasource.metadata; + +public record SchemaMetadata(String catalog, String name) { +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java index d019005..74f85d6 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLActuator.java @@ -17,16 +17,22 @@ public interface SQLActuator { List getObjects(String sql, Class clazz, Map fieldMapping) throws SQLException; + List> queryRows(String sql, List parameters) throws SQLException; + int count(SQL sql) throws SQLException; int count(SQLExecutePlan plan) throws SQLException; SQLQueryResult execute(SQLExecutePlan plan) throws SQLException; + SQLQueryResult executeRaw(SQLExecutePlan plan) throws SQLException; + SQLQueryResult execute(SQL sql) throws SQLException; SQLQueryResult executeWithAudit(SQLExecutePlan plan) throws SQLException; + SQLQueryResult executeRawWithAudit(SQLExecutePlan plan) throws SQLException; + SQLQueryResult executeWithAudit(SQL sql) throws SQLException; SQLActuator withConnection(Connection connection); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java index 89aafeb..23c292b 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLExecutePlan.java @@ -26,7 +26,8 @@ public class SQLExecutePlan { private SQLActuator sqlActuator; private String targetSQL; private final DbType druidDbType; - private Statement statement; + private volatile Statement statement; + private volatile boolean cancelled; private Connection connection; private ACLResult aclResult; @@ -85,7 +86,10 @@ public SQLQueryResult executeWithAudit() throws SQLException { } - public Statement createStatement() throws SQLException { + public synchronized Statement createStatement() throws SQLException { + if (this.cancelled) { + throw new SQLException(MessageUtils.get("ExecutionCanceled")); + } if (this.statement == null || this.statement.isClosed()) { this.statement = this.connection.createStatement(); } @@ -97,7 +101,14 @@ public SQLStatement getTargetSQLStatement() { } public void cancel() throws SQLException { - this.statement.cancel(); + Statement currentStatement; + synchronized (this) { + this.cancelled = true; + currentStatement = this.statement; + } + if (currentStatement != null && !currentStatement.isClosed()) { + currentStatement.cancel(); + } } public void close() { diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java index 2b7a199..b065b1e 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryParams.java @@ -14,4 +14,5 @@ public class SQLQueryParams { private int limit; private int timeout = -1; + private String filter; } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java index 0177071..8193806 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/datasource/sql/SQLQueryResult.java @@ -8,6 +8,7 @@ import java.sql.Time; import java.util.ArrayList; import java.util.List; +import java.util.StringJoiner; @Data @@ -15,10 +16,13 @@ public class SQLQueryResult { private String sql; private int total = -1; private boolean paged; + private boolean truncated; + private int rowLimit; private int updateCount; private boolean hasResultSet = true; private List fields = new ArrayList<>(); private List> data = new ArrayList<>(); + private List results = new ArrayList<>(); private Time startTime; private Time endTime; @@ -48,6 +52,14 @@ public SQLQueryResult(String sql) { } public String getOutput() { + if (!this.results.isEmpty()) { + StringJoiner output = new StringJoiner(System.lineSeparator()); + for (int index = 0; index < this.results.size(); index++) { + output.add("Result " + (index + 1) + ":"); + output.add(this.results.get(index).getOutput()); + } + return output.toString(); + } if (!this.hasResultSet) { return String.format("Query OK, %d rows affected", this.updateCount); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java index baa5ae2..10919cc 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/ACLFilter.java @@ -1,9 +1,17 @@ package org.jumpserver.chen.framework.jms; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import java.sql.Connection; public interface ACLFilter { - ACLResult commandACLFilter(String command, Connection connection); + default ACLResult commandACLFilter(String command, Connection connection) { + ACLCommandContext context = connection == null + ? ACLCommandContext.executionOwned(null) + : ACLCommandContext.queryConsoleOwned(connection); + return this.commandACLFilterWithContext(command, context); + } + + ACLResult commandACLFilterWithContext(String command, ACLCommandContext context); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLCommandContext.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLCommandContext.java new file mode 100644 index 0000000..ab2b6ac --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/acl/ACLCommandContext.java @@ -0,0 +1,70 @@ +package org.jumpserver.chen.framework.jms.acl; + +import org.jumpserver.chen.framework.datasource.edit.ConnectionOwnership; + +import java.sql.Connection; +import java.util.Objects; +import java.util.OptionalInt; + +public record ACLCommandContext( + Connection connection, + ConnectionOwnership connectionOwnership, + OptionalInt affectedRows, + String reviewBatchSql +) { + public ACLCommandContext { + Objects.requireNonNull(connectionOwnership, "connectionOwnership"); + Objects.requireNonNull(affectedRows, "affectedRows"); + if (affectedRows.isPresent() && affectedRows.getAsInt() < 0) { + throw new IllegalArgumentException("affectedRows must not be negative"); + } + } + + public ACLCommandContext( + Connection connection, + ConnectionOwnership connectionOwnership, + OptionalInt affectedRows + ) { + this(connection, connectionOwnership, affectedRows, null); + } + + public static ACLCommandContext executionOwned(Connection connection) { + return new ACLCommandContext( + connection, + ConnectionOwnership.EXECUTION_CONTEXT, + OptionalInt.empty(), + null + ); + } + + public static ACLCommandContext queryConsoleOwned(Connection connection) { + return new ACLCommandContext( + Objects.requireNonNull(connection, "connection"), + ConnectionOwnership.QUERY_CONSOLE, + OptionalInt.empty(), + null + ); + } + + public static ACLCommandContext planned( + Connection connection, + ConnectionOwnership connectionOwnership, + int affectedRows + ) { + return planned(connection, connectionOwnership, affectedRows, null); + } + + public static ACLCommandContext planned( + Connection connection, + ConnectionOwnership connectionOwnership, + int affectedRows, + String reviewBatchSql + ) { + return new ACLCommandContext( + connection, + connectionOwnership, + OptionalInt.of(affectedRows), + reviewBatchSql + ); + } +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java index 2403eff..20ea450 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/ACLFilterImpl.java @@ -1,22 +1,26 @@ package org.jumpserver.chen.framework.jms.impl; import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.datasource.edit.ConnectionOwnership; import org.jumpserver.chen.framework.datasource.sql.SQL; import org.jumpserver.chen.framework.i18n.MessageUtils; import org.jumpserver.chen.framework.jms.ACLFilter; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Button; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; import org.jumpserver.wisp.Common; import org.jumpserver.wisp.ServiceGrpc; import org.jumpserver.wisp.ServiceOuterClass; -import java.sql.Connection; import java.util.List; +import java.util.OptionalInt; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; @@ -24,6 +28,8 @@ @Slf4j public class ACLFilterImpl implements ACLFilter { + private static final long REVIEW_DECISION_TIMEOUT_SECONDS = 300; + private static final long REVIEW_RPC_TIMEOUT_SECONDS = 30; private final ServiceGrpc.ServiceBlockingStub serviceBlockingStub; private final List commandACLs; @@ -39,7 +45,7 @@ public ACLFilterImpl(Common.Session session, ServiceGrpc.ServiceBlockingStub ser private static final String REJECT_MESSAGE = "reject by acl rule"; @Override - public ACLResult commandACLFilter(String command, Connection connection) { + public ACLResult commandACLFilterWithContext(String command, ACLCommandContext context) { var result = new ACLResult(); var acl = this.matchRule(command, result); @@ -63,44 +69,85 @@ public ACLResult commandACLFilter(String command, Connection connection) { result.setRiskLevel(Common.RiskLevel.Reject); } case Review -> { - var countDownLatch = new CountDownLatch(1); + var decisionLatch = new CountDownLatch(1); + var resultLatch = new CountDownLatch(1); AtomicReference exception = new AtomicReference<>(null); + AtomicBoolean submitted = new AtomicBoolean(false); + AtomicReference dialogHandle = new AtomicReference<>(DialogHandle.NOOP); + var controller = SessionManager.getCurrentSession().getController(); + String dialogOwner = controller.getDialogOwner(); var dialog = new Dialog(MessageUtils.get("CommandReview")); dialog.setBody(MessageUtils.get("CommandReviewMessage")); dialog.addButton(new Button(MessageUtils.get("Submit"), "submit", () -> { - + if (!submitted.compareAndSet(false, true)) { + return; + } + dialogHandle.get().close(); var token = SessionManager.getContextToken(); new Thread(() -> { SessionManager.setContext(token); + controller.bindDialogOwner(dialogOwner); try { - this.createAndWaitTicket(command, acl, connection); + if (controller.isDialogOwnerCancelled(dialogOwner)) { + throw new RuntimeException(MessageUtils.get("UserCancelCommandReviewError")); + } + this.createAndWaitTicket(command, acl, context); } catch (Exception e) { exception.set(e); } finally { - countDownLatch.countDown(); + resultLatch.countDown(); + controller.clearDialogOwner(); } }).start(); + decisionLatch.countDown(); })); dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { - exception.set(new RuntimeException(MessageUtils.get("UserCancelCommandReviewError"))); - countDownLatch.countDown(); + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("UserCancelCommandReviewError")) + ); + decisionLatch.countDown(); + resultLatch.countDown(); })); - SessionManager.getCurrentSession().getController().showDialog(dialog); + dialogHandle.set(controller.showDialog(dialog, () -> { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("UserCancelCommandReviewError")) + ); + decisionLatch.countDown(); + resultLatch.countDown(); + })); try { - countDownLatch.await(); - if (exception.get() != null) { + if (!decisionLatch.await(REVIEW_DECISION_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("CommandReviewTimeoutError")) + ); + dialogHandle.get().cancel(); + } + if (submitted.get() && exception.get() == null && + !resultLatch.await(WAIT_TICKET_TIMEOUT, TimeUnit.MILLISECONDS)) { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("CommandReviewTimeoutError")) + ); + controller.cancelCurrentDialog(dialogOwner); + } + if (!submitted.get() || exception.get() != null) { result.setRiskLevel(Common.RiskLevel.ReviewReject); } else { result.setRiskLevel(Common.RiskLevel.ReviewAccept); } } catch (InterruptedException e) { - throw new RuntimeException(e); + Thread.currentThread().interrupt(); + controller.cancelCurrentDialog(dialogOwner); + result.setRiskLevel(Common.RiskLevel.ReviewReject); } finally { - SessionManager.getCurrentSession().getController().closeDialog(); + dialogHandle.get().close(); } } } @@ -110,26 +157,14 @@ public ACLResult commandACLFilter(String command, Connection connection) { } - private void createAndWaitTicket(String command, Common.CommandACL commandACL, Connection connection) { - var affectRows = 0; - try { - var sqlActuator = SessionManager.getCurrentSession() - .getDatasource() - .getConnectionManager() - .getSqlActuator(); - if (connection != null) { - sqlActuator = sqlActuator.withConnection(connection); - } - affectRows = sqlActuator.getAffectedRows(SQL.of(command)); - } catch (Exception e) { - log.error("get affected rows failed", e); - } - - var input = command; - if (affectRows != -1) { - input = String.format("Affected rows: %d\n%s", affectRows, command); - } + private void createAndWaitTicket(String command, Common.CommandACL commandACL, ACLCommandContext context) { + OptionalInt affectedRows = this.estimateAffectedRows(command, context); + var input = reviewTicketCommand(command, context); + String affectedRowsValue = affectedRows.isPresent() + ? Integer.toString(affectedRows.getAsInt()) + : "unknown"; + input = String.format("Affected rows: %s\n%s", affectedRowsValue, input); var req = ServiceOuterClass.CommandConfirmRequest .newBuilder() @@ -137,35 +172,61 @@ private void createAndWaitTicket(String command, Common.CommandACL commandACL, C .setSessionId(this.session.getId()) .setCmdAclId(commandACL.getId()) .build(); - var resp = this.serviceBlockingStub.createCommandTicket(req); + var resp = this.serviceBlockingStub + .withDeadlineAfter(REVIEW_RPC_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .createCommandTicket(req); if (!resp.getStatus().getOk()) { throw new RuntimeException("create command ticket failed: " + resp.getStatus().getErr()); } - this.waitForTicketStatusChange(command, resp.getInfo()); + this.waitForTicketStatusChange(resp.getInfo()); + } + + OptionalInt estimateAffectedRows(String command, ACLCommandContext context) { + if (context.affectedRows().isPresent()) { + return context.affectedRows(); + } + if (context.connectionOwnership() == ConnectionOwnership.QUERY_CONSOLE) { + return OptionalInt.empty(); + } + + try { + var sqlActuator = SessionManager.getCurrentSession() + .getDatasource() + .getConnectionManager() + .getSqlActuator(); + if (context.connection() != null) { + sqlActuator = sqlActuator.withConnection(context.connection()); + } + int affectedRows = sqlActuator.getAffectedRows(SQL.of(command)); + return affectedRows < 0 ? OptionalInt.empty() : OptionalInt.of(affectedRows); + } catch (Exception e) { + log.warn("get affected rows failed: {}", e.getClass().getSimpleName()); + return OptionalInt.empty(); + } + } + + private String reviewTicketCommand(String command, ACLCommandContext context) { + String batchSql = context.reviewBatchSql(); + if (batchSql == null || batchSql.isBlank() || batchSql.equals(command)) { + return command; + } + return String.format("Batch SQL:\n%s\n\nReview-triggering SQL:\n%s", batchSql, command); } - private void openCommandReviewEvent(Runnable cancel, String command, long startTIme, long endTime) { + private DialogHandle openCommandReviewEvent(Runnable cancel) { var dialog = new Dialog(MessageUtils.get("CommandReview")); dialog.setBody(MessageUtils.get("WaitCommandReviewMessage")); dialog.addButton(new Button(MessageUtils.get("Cancel"), "cancel", () -> { cancel.run(); - SessionManager.getCurrentSession().getController().closeDialog(); })); - SessionManager.getCurrentSession().getController().showDialog(dialog); - } - - private void closeCommandReviewEvent() { - SessionManager.getCurrentSession().getController().closeDialog(); + return SessionManager.getCurrentSession().getController().showDialog(dialog, cancel); } private static final long WAIT_TICKET_TIMEOUT = 30 * 60 * 1000; private static final long WAIT_TICKET_INTERVAL = 5 * 1000; - private void waitForTicketStatusChange(String cmd, ServiceOuterClass.TicketInfo ticketInfo) { - - long startTime = System.currentTimeMillis(); - long endTime = startTime + WAIT_TICKET_TIMEOUT; + private void waitForTicketStatusChange(ServiceOuterClass.TicketInfo ticketInfo) { CountDownLatch cdl = new CountDownLatch(1); Timer timer = new Timer(); @@ -173,12 +234,11 @@ private void waitForTicketStatusChange(String cmd, ServiceOuterClass.TicketInfo final AtomicBoolean ticketClosed = new AtomicBoolean(false); AtomicReference exception = new AtomicReference<>(null); - this.openCommandReviewEvent(() -> { + DialogHandle dialogHandle = this.openCommandReviewEvent(() -> { exception.set(new RuntimeException(MessageUtils.get("UserCancelCommandReviewError"))); cdl.countDown(); timer.cancel(); - - }, cmd, startTime, endTime); + }); try { var stub = this.serviceBlockingStub; @@ -189,49 +249,63 @@ private void waitForTicketStatusChange(String cmd, ServiceOuterClass.TicketInfo public void run() { SessionManager.setContext(token); - if (System.currentTimeMillis() > endTime) { - exception.set(new RuntimeException(MessageUtils.get("CommandReviewTimeoutError"))); - timer.cancel(); - cdl.countDown(); - } - var checkRequest = ServiceOuterClass .TicketRequest.newBuilder() .setReq(ticketInfo.getCheckReq()) .build(); - var checkResponse = stub.checkTicketState(checkRequest); - - if (!checkResponse.getStatus().getOk()) { - throw new RuntimeException("Failed to check ticket status: " + checkResponse.getStatus().getErr()); - } + try { + var checkResponse = stub + .withDeadlineAfter(REVIEW_RPC_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .checkTicketState(checkRequest); - switch (checkResponse.getData().getState()) { - case Approved -> { - ticketClosed.set(true); - timer.cancel(); - cdl.countDown(); + if (!checkResponse.getStatus().getOk()) { + throw new RuntimeException( + "Failed to check ticket status: " + checkResponse.getStatus().getErr() + ); } - case Rejected, Closed -> { - ticketClosed.set(true); - exception.set(new RuntimeException(MessageUtils.get("CommandReviewRejectBy", checkResponse.getData().getProcessor()))); - timer.cancel(); - cdl.countDown(); + + switch (checkResponse.getData().getState()) { + case Approved -> { + ticketClosed.set(true); + timer.cancel(); + cdl.countDown(); + } + case Rejected, Closed -> { + ticketClosed.set(true); + exception.set(new RuntimeException(MessageUtils.get( + "CommandReviewRejectBy", + checkResponse.getData().getProcessor() + ))); + timer.cancel(); + cdl.countDown(); + } } + } catch (RuntimeException e) { + exception.compareAndSet(null, e); + timer.cancel(); + cdl.countDown(); } } }, 0, WAIT_TICKET_INTERVAL); try { - cdl.await(); + if (!cdl.await(WAIT_TICKET_TIMEOUT, TimeUnit.MILLISECONDS)) { + exception.compareAndSet( + null, + new RuntimeException(MessageUtils.get("CommandReviewTimeoutError")) + ); + } if (exception.get() != null) { throw exception.get(); } } catch (InterruptedException e) { - log.error("wait for ticket status change failed: {}", e.getMessage()); + Thread.currentThread().interrupt(); + throw new RuntimeException(e); } } finally { - this.closeCommandReviewEvent(); + timer.cancel(); + dialogHandle.close(); if (!ticketClosed.get()) { this.closeTicket(ticketInfo); } @@ -242,7 +316,9 @@ private void closeTicket(ServiceOuterClass.TicketInfo ticketInfo) { var cancelRequest = ServiceOuterClass.TicketRequest.newBuilder() .setReq(ticketInfo.getCancelReq()) .build(); - var cancelResponse = this.serviceBlockingStub.cancelTicket(cancelRequest); + var cancelResponse = this.serviceBlockingStub + .withDeadlineAfter(REVIEW_RPC_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .cancelTicket(cancelRequest); if (!cancelResponse.getStatus().getOk()) { log.error("close ticket failed: {}", cancelResponse.getStatus().getErr()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java index 92e84e9..62fa0af 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/jms/impl/CommandHandlerImpl.java @@ -6,10 +6,12 @@ import org.jumpserver.wisp.Common; import org.jumpserver.wisp.ServiceGrpc; import org.jumpserver.wisp.ServiceOuterClass; -import org.springframework.scheduling.annotation.Async; + +import java.util.concurrent.TimeUnit; @Slf4j public class CommandHandlerImpl implements CommandHandler { + private static final long COMMAND_UPLOAD_TIMEOUT_SECONDS = 30; private final Common.Session session; private final ServiceGrpc.ServiceBlockingStub serviceBlockingStub; @@ -20,7 +22,6 @@ public CommandHandlerImpl(Common.Session session, ServiceGrpc.ServiceBlockingStu } @Override - @Async public void recordCommand(CommandRecord commandRecord) { var reqBuilder = ServiceOuterClass.CommandRequest @@ -40,7 +41,9 @@ public void recordCommand(CommandRecord commandRecord) { reqBuilder.setCmdGroupId(commandRecord.getCmdGroupId()); } - var resp = this.serviceBlockingStub.uploadCommand(reqBuilder.build()); + var resp = this.serviceBlockingStub + .withDeadlineAfter(COMMAND_UPLOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .uploadCommand(reqBuilder.build()); if (!resp.getStatus().getOk()) { throw new RuntimeException("upload command failed: " + resp.getStatus().getErr()); } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java index b0ba5e8..50e4e84 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/Session.java @@ -3,6 +3,7 @@ import org.jumpserver.chen.framework.console.Console; import org.jumpserver.chen.framework.datasource.Datasource; import org.jumpserver.chen.framework.datasource.sql.SQLQueryResult; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.jms.exception.CommandRejectException; @@ -79,6 +80,8 @@ public interface Session { boolean isActive(); + boolean isClosing(); + void close(); void close(String message, Object... args); @@ -94,6 +97,10 @@ public interface Session { ACLResult checkACL(String command, Connection connection); + default ACLResult checkACLWithContext(String command, ACLCommandContext context) { + return this.checkACL(command, context.connection()); + } + boolean enableAutoComplete(); void setEnableAutoComplete(boolean enableAutoComplete); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java index 659ad02..ad2bea0 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/SessionManager.java @@ -1,10 +1,12 @@ package org.jumpserver.chen.framework.session; import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.framework.console.Console; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; @Slf4j @@ -31,6 +33,20 @@ public static void unregisterSession(String token) { log.info("session {} unregistered, current session count {}", token, instance.getCurrentSessionCount()); } + public static boolean registerConsole(String token, String consoleId, Console console) { + var registered = new AtomicBoolean(false); + instance.store.computeIfPresent(token, (ignored, session) -> { + synchronized (session) { + if (!session.isClosing()) { + session.getConsoles().put(consoleId, console); + registered.set(true); + } + } + return session; + }); + return registered.get(); + } + public int getCurrentSessionCount() { return instance.store.size(); } @@ -48,7 +64,8 @@ public static SessionManager getInstance() { } public static Session getCurrentSession() { - return instance.store.get(token.get()); + String currentToken = token.get(); + return currentToken == null ? null : instance.store.get(currentToken); } public static Map getStore() { diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java index 5c7ce46..5e97c0c 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/Controller.java @@ -7,7 +7,37 @@ public interface Controller { void showDialog(Dialog dialog); + default DialogHandle showDialog(Dialog dialog, Runnable onCancel) { + this.showDialog(dialog); + return DialogHandle.NOOP; + } + void closeDialog(); + + default void bindDialogOwner(String owner) { + } + + default String getDialogOwner() { + return null; + } + + default void clearDialogOwner() { + } + + default void cancelDialogs(String owner) { + } + + default void cancelCurrentDialog(String owner) { + this.cancelDialogs(owner); + } + + default boolean isDialogOwnerCancelled(String owner) { + return false; + } + + default void cancelAllDialogs() { + } + void showMessage(MessageLevel level, String message); void handlePacket(Packet packet); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/DialogHandle.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/DialogHandle.java new file mode 100644 index 0000000..f40c148 --- /dev/null +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/DialogHandle.java @@ -0,0 +1,18 @@ +package org.jumpserver.chen.framework.session.controller; + +public interface DialogHandle extends AutoCloseable { + DialogHandle NOOP = new DialogHandle() { + @Override + public void cancel() { + } + + @Override + public void close() { + } + }; + + void cancel(); + + @Override + void close(); +} diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java index 44e5d88..c0642c5 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/dialog/Dialog.java @@ -10,6 +10,7 @@ @Data public class Dialog { + private String id; private String title = "提示"; private String width = "30%"; private String body; diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java index 629f0f8..8317de6 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/controller/impl/BaseController.java @@ -1,18 +1,31 @@ package org.jumpserver.chen.framework.session.controller.impl; +import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.session.controller.Controller; +import org.jumpserver.chen.framework.session.controller.DialogHandle; import org.jumpserver.chen.framework.session.controller.dialog.Dialog; import org.jumpserver.chen.framework.session.controller.message.Message; import org.jumpserver.chen.framework.session.controller.message.MessageLevel; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.chen.framework.ws.io.PacketIO; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + @Slf4j public class BaseController implements Controller { + private static final Gson GSON = new Gson(); + private static final String SESSION_DIALOG_OWNER = "session"; + private final PacketIO packetIO; - private Dialog currentDialog; + private final Object dialogLock = new Object(); + private final ThreadLocal dialogOwner = new ThreadLocal<>(); + private final Set cancelledOwners = new HashSet<>(); + private ActiveDialog currentDialog; + private boolean allDialogsCancelled; public BaseController(PacketIO packetIO) { this.packetIO = packetIO; @@ -20,14 +33,123 @@ public BaseController(PacketIO packetIO) { @Override public void showDialog(Dialog dialog) { - this.currentDialog = dialog; - this.packetIO.sendPacket("show_dialog", dialog); + this.showDialog(dialog, () -> { + }); + } + + @Override + public DialogHandle showDialog(Dialog dialog, Runnable onCancel) { + String id = UUID.randomUUID().toString(); + dialog.setId(id); + ActiveDialog activeDialog = new ActiveDialog( + id, + this.dialogOwner.get() == null ? SESSION_DIALOG_OWNER : this.dialogOwner.get(), + dialog, + onCancel + ); + ActiveDialog replaced; + boolean ownerCancelled; + synchronized (this.dialogLock) { + ownerCancelled = this.allDialogsCancelled || this.cancelledOwners.contains(activeDialog.owner()); + if (ownerCancelled) { + replaced = null; + } else { + replaced = this.currentDialog; + this.currentDialog = activeDialog; + this.packetIO.sendPacket("show_dialog", dialog); + } + } + if (ownerCancelled) { + runCancel(activeDialog); + return DialogHandle.NOOP; + } + if (replaced != null) { + log.warn("replace unfinished dialog, oldDialogId={}, newDialogId={}", replaced.id(), id); + runCancel(replaced); + } + return new DialogHandle() { + @Override + public void cancel() { + BaseController.this.finishDialog(id, true); + } + + @Override + public void close() { + BaseController.this.finishDialog(id, false); + } + }; } @Override public void closeDialog() { - this.currentDialog = null; - this.packetIO.sendPacket("close_dialog", null); + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + activeDialog = this.currentDialog; + if (activeDialog == null) { + this.packetIO.sendPacket("close_dialog", null); + return; + } + } + this.finishDialog(activeDialog.id(), true); + } + + @Override + public void bindDialogOwner(String owner) { + this.dialogOwner.set(owner); + } + + @Override + public String getDialogOwner() { + return this.dialogOwner.get() == null ? SESSION_DIALOG_OWNER : this.dialogOwner.get(); + } + + @Override + public void clearDialogOwner() { + this.dialogOwner.remove(); + } + + @Override + public void cancelDialogs(String owner) { + synchronized (this.dialogLock) { + this.cancelledOwners.add(owner); + } + this.cancelCurrentDialog(owner); + } + + @Override + public void cancelCurrentDialog(String owner) { + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + activeDialog = this.currentDialog; + if (activeDialog == null || !activeDialog.owner().equals(owner)) { + return; + } + this.currentDialog = null; + this.packetIO.sendPacket("close_dialog", null); + } + runCancel(activeDialog); + } + + @Override + public boolean isDialogOwnerCancelled(String owner) { + synchronized (this.dialogLock) { + return this.allDialogsCancelled || this.cancelledOwners.contains(owner); + } + } + + @Override + public void cancelAllDialogs() { + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + this.allDialogsCancelled = true; + activeDialog = this.currentDialog; + if (activeDialog == null) { + return; + } + this.currentDialog = null; + this.packetIO.sendPacket("close_dialog", null); + } + runCancel(activeDialog); } @Override @@ -38,9 +160,17 @@ public void showMessage(MessageLevel level, String message) { @Override public void handlePacket(Packet packet) { switch (packet.getType()) { - case "dialog_event": - this.onDialogEvent((String) packet.getData()); + case "dialog_event": { + try { + var event = GSON.fromJson(GSON.toJson(packet.getData()), DialogEvent.class); + if (event != null) { + this.onDialogEvent(event.dialogId(), event.event()); + } + } catch (RuntimeException e) { + log.warn("ignore invalid dialog event"); + } break; + } case "input_active": this.refreshLastActiveTime(); break; @@ -60,19 +190,51 @@ public void sendFile(String fileKey) { this.packetIO.sendPacket("download", fileKey); } - private void onDialogEvent(String event) { - if (this.currentDialog == null) { - this.closeDialog(); - return; + private void onDialogEvent(String dialogId, String event) { + synchronized (this.dialogLock) { + ActiveDialog activeDialog = this.currentDialog; + if (activeDialog == null || !activeDialog.id().equals(dialogId)) { + log.warn("ignore stale dialog event, dialogId={}, event={}", dialogId, event); + return; + } + try { + var method = activeDialog.dialog().getEvent(event); + if (method != null) { + method.run(); + } + } catch (Exception e) { + this.showMessage(MessageLevel.ERROR, "handle event %s error:".formatted(event) + e.getMessage()); + } } - try { - var method = this.currentDialog.getEvent(event); - if (method != null) { - method.run(); + } + + private void finishDialog(String id, boolean cancel) { + ActiveDialog activeDialog; + synchronized (this.dialogLock) { + if (this.currentDialog == null || !this.currentDialog.id().equals(id)) { + return; } - } catch (Exception e) { - this.showMessage(MessageLevel.ERROR, "handle event %s error:".formatted(event) + e.getMessage()); + activeDialog = this.currentDialog; + this.currentDialog = null; + this.packetIO.sendPacket("close_dialog", null); + } + if (cancel) { + runCancel(activeDialog); + } + } + + private static void runCancel(ActiveDialog activeDialog) { + try { + activeDialog.onCancel().run(); + } catch (RuntimeException e) { + log.warn("cancel dialog failed, dialogId={}", activeDialog.id(), e); } } + private record ActiveDialog(String id, String owner, Dialog dialog, Runnable onCancel) { + } + + private record DialogEvent(String dialogId, String event) { + } + } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java index 1dd8b52..fc6aac2 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/BaseSession.java @@ -23,10 +23,12 @@ import java.sql.Connection; import java.sql.SQLException; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; @Slf4j public class BaseSession implements Session { @@ -44,6 +46,7 @@ public class BaseSession implements Session { @Getter Map consoles = new ConcurrentHashMap<>(); + private final AtomicBoolean closeStarted = new AtomicBoolean(false); @Getter @Setter @@ -199,8 +202,30 @@ public boolean isActive() { return this.getPacketIO() != null && this.getPacketIO().getWsSession().isOpen(); } + @Override + public boolean isClosing() { + return this.closeStarted.get(); + } + @Override public void close() { + if (!this.beginClose()) { + return; + } + this.closeSessionResources(); + } + + protected final boolean beginClose() { + synchronized (this) { + return this.closeStarted.compareAndSet(false, true); + } + } + + protected final void closeSessionResources() { + if (this.getController() != null) { + this.getController().cancelAllDialogs(); + } + this.closeConsoles(); SessionManager.unregisterSession(this.getWebToken()); this.getDatasource().close(); this.getPacketIO().close(); @@ -210,6 +235,24 @@ public void close() { } } + protected final void closeConsoles() { + var detached = new ArrayList>(); + while (!this.consoles.isEmpty()) { + for (var entry : this.consoles.entrySet()) { + if (this.consoles.remove(entry.getKey(), entry.getValue())) { + detached.add(Map.entry(entry.getKey(), entry.getValue())); + } + } + } + for (var entry : detached) { + try { + entry.getValue().close(); + } catch (RuntimeException e) { + log.warn("close console failed, consoleId={}", entry.getKey(), e); + } + } + } + @Override public void close(String message, Object... args) { this.close(); diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java index 5f377b1..252f256 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/session/impl/JMSSession.java @@ -9,6 +9,7 @@ import org.jumpserver.chen.framework.jms.ACLFilter; import org.jumpserver.chen.framework.jms.CommandHandler; import org.jumpserver.chen.framework.jms.ReplayHandler; +import org.jumpserver.chen.framework.jms.acl.ACLCommandContext; import org.jumpserver.chen.framework.jms.acl.ACLResult; import org.jumpserver.chen.framework.jms.entity.CommandRecord; import org.jumpserver.chen.framework.jms.exception.CommandRejectException; @@ -150,11 +151,16 @@ public void recordCommand(String command) { @Override public ACLResult checkACL(String command) { - return this.aclFilter.commandACLFilter(command, null); + return this.aclFilter.commandACLFilterWithContext(command, ACLCommandContext.executionOwned(null)); } public ACLResult checkACL(String command, Connection connection) { - return this.aclFilter.commandACLFilter(command, connection); + return this.aclFilter.commandACLFilterWithContext(command, ACLCommandContext.queryConsoleOwned(connection)); + } + + @Override + public ACLResult checkACLWithContext(String command, ACLCommandContext context) { + return this.aclFilter.commandACLFilterWithContext(command, context); } @Override @@ -261,6 +267,17 @@ private void startWaitIdleTime() { @Override public void close() { + if (!this.beginClose()) { + return; + } + this.closeJmsSessionResources(); + } + + private void closeJmsSessionResources() { + if (this.getController() != null) { + this.getController().cancelAllDialogs(); + } + this.closeConsoles(); try { this.replayHandler.release(); this.finishedJmsSession(); @@ -270,23 +287,27 @@ public void close() { } } finally { - super.close(); + super.closeSessionResources(); } } public void close(String message, String reason, Object... args) { + if (!this.beginClose()) { + return; + } SessionManager.setContext(this.getWebToken()); + try { + this.getPacketIO().sendPacket("session_close", null); - this.getPacketIO().sendPacket("session_close", null); - - var dialog = new Dialog(MessageUtils.get("SessionFinished")); - dialog.setBody(MessageUtils.get(message, args)); - this.getController().showDialog(dialog); - - this.recordLifecycle(ServiceOuterClass.SessionLifecycleLogRequest.EventType.AssetConnectFinished, reason); - this.closed = true; + var dialog = new Dialog(MessageUtils.get("SessionFinished")); + dialog.setBody(MessageUtils.get(message, args)); + this.getController().showDialog(dialog); - this.close(); + this.recordLifecycle(ServiceOuterClass.SessionLifecycleLogRequest.EventType.AssetConnectFinished, reason); + this.closed = true; + } finally { + this.closeJmsSessionResources(); + } } private void finishedJmsSession() { @@ -328,6 +349,7 @@ public SQLQueryResult withAudit(String command, QueryAuditFunction queryAuditFun } CommandRecord commandRecord = new CommandRecord(command); + Throwable primaryFailure = null; try { this.replayHandler.writeInput(commandRecord.getInput()); @@ -342,12 +364,29 @@ public SQLQueryResult withAudit(String command, QueryAuditFunction queryAuditFun this.replayHandler.writeOutput(result.getOutput()); return result; - } catch (SQLException e) { + } catch (SQLException | RuntimeException e) { + primaryFailure = e; commandRecord.setError(e.getMessage()); - this.replayHandler.writeOutput(e.getMessage()); + this.writeReplayFailure(e.getMessage(), e); throw e; } finally { - this.commandHandler.recordCommand(commandRecord); + try { + this.commandHandler.recordCommand(commandRecord); + } catch (RuntimeException auditFailure) { + if (primaryFailure != null) { + primaryFailure.addSuppressed(auditFailure); + } else { + throw auditFailure; + } + } + } + } + + private void writeReplayFailure(String output, Throwable primaryFailure) { + try { + this.replayHandler.writeOutput(output); + } catch (RuntimeException replayFailure) { + primaryFailure.addSuppressed(replayFailure); } } } diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java index ad24e8b..bf30b60 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/utils/PageUtils.java @@ -15,7 +15,6 @@ import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter; import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGSelectQueryBlock; import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock; -import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerTop; import com.alibaba.druid.util.JdbcUtils; import java.util.Iterator; @@ -40,6 +39,36 @@ public static String count(String sql, DbType dbType) { } } + public static String filter(String sql, DbType dbType, String conditionSql) { + if (conditionSql == null || conditionSql.isBlank()) { + return sql; + } + + SQLStatement sourceStatement = SQLUtils.parseSingleStatement(sql, dbType); + if (!(sourceStatement instanceof SQLSelectStatement sourceSelect) || + !(sourceSelect.getSelect().getQuery() instanceof SQLSelectQueryBlock sourceQuery)) { + throw new IllegalArgumentException("Data view filter requires a simple SELECT query"); + } + + List filterStatements = SQLUtils.parseStatements( + "SELECT * FROM CHEN_FILTER_SOURCE WHERE " + conditionSql, + dbType + ); + if (filterStatements.size() != 1 || !(filterStatements.get(0) instanceof SQLSelectStatement filterSelect) || + !(filterSelect.getSelect().getQuery() instanceof SQLSelectQueryBlock filterQuery) || + filterQuery.getWhere() == null || filterQuery.getGroupBy() != null || + filterQuery.getOrderBy() != null || filterQuery.getLimit() != null) { + throw new IllegalArgumentException("Invalid data view WHERE condition"); + } + + SQLExpr condition = filterQuery.getWhere().clone(); + SQLExpr current = sourceQuery.getWhere(); + sourceQuery.setWhere(current == null + ? condition + : new SQLBinaryOpExpr(current, SQLBinaryOperator.BooleanAnd, condition, dbType)); + return SQLUtils.toSQLString(sourceStatement, dbType); + } + public static String limit(String sql, DbType dbType, int offset, int count) { List stmtList = SQLUtils.parseStatements(sql, dbType); if (stmtList.size() != 1) { @@ -264,14 +293,14 @@ private static boolean limitSQLServer(SQLSelect select, DbType dbType, int offse if (query instanceof SQLSelectQueryBlock) { queryBlock = (SQLServerSelectQueryBlock) query; if (offset <= 0) { - SQLServerTop top = queryBlock.getTop(); + SQLTop top = queryBlock.getTop(); if (check && top != null && !top.isPercent() && top.getExpr() instanceof SQLNumericLiteralExpr) { int rowCount = ((SQLNumericLiteralExpr) top.getExpr()).getNumber().intValue(); if (rowCount <= count) { return false; } } - queryBlock.setTop(new SQLServerTop(new SQLNumberExpr(count))); + queryBlock.setTop(new SQLTop(new SQLNumberExpr(count))); return true; } else { // 创建 SELECT NULL 的子查询 @@ -303,7 +332,7 @@ private static boolean limitSQLServer(SQLSelect select, DbType dbType, int offse } else { queryBlock = new SQLServerSelectQueryBlock(); if (offset <= 0) { - queryBlock.setTop(new SQLServerTop(new SQLNumberExpr(count))); + queryBlock.setTop(new SQLTop(new SQLNumberExpr(count))); select.setQuery(queryBlock); return true; } else { diff --git a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java index e03911f..d296c1b 100644 --- a/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java +++ b/backend/framework/src/main/java/org/jumpserver/chen/framework/ws/ConsoleWebSocketHandler.java @@ -6,23 +6,26 @@ import org.jumpserver.chen.framework.console.Console; import org.jumpserver.chen.framework.console.DataViewConsole; import org.jumpserver.chen.framework.console.QueryConsole; +import org.jumpserver.chen.framework.console.action.QueryConsoleAction; +import org.jumpserver.chen.framework.console.context.ConsoleContext; +import org.jumpserver.chen.framework.console.context.ConsoleContextResolutionException; +import org.jumpserver.chen.framework.console.context.ConsoleContextResolver; import org.jumpserver.chen.framework.console.entity.request.Connect; -import org.jumpserver.chen.framework.datasource.ResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.session.controller.message.Message; import org.jumpserver.chen.framework.session.controller.message.MessageLevel; import org.jumpserver.chen.framework.ws.io.Packet; import org.jumpserver.chen.framework.ws.io.PacketIO; -import org.jumpserver.chen.framework.utils.TreeUtils; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; -import java.sql.SQLException; -import java.util.Set; +import java.util.ArrayDeque; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -30,11 +33,6 @@ public class ConsoleWebSocketHandler extends TextWebSocketHandler { private static final Gson GSON = new Gson(); - private static final Set QUERY_NODE_TYPES = - Set.of("datasource", "database", "schema", "table"); - private static final Set DATA_VIEW_NODE_TYPES = Set.of("table", "view"); - - @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { if (session instanceof NativeWebSocketSession ns) { @@ -47,115 +45,214 @@ public void afterConnectionEstablished(WebSocketSession session) throws Exceptio private final ExecutorService executorService = Executors.newFixedThreadPool(10); + private final Map sessionExecutors = new ConcurrentHashMap<>(); @Override public void handleMessage(WebSocketSession session, WebSocketMessage message) throws Exception { + var token = (String) session.getAttributes().get("token"); + var packet = GSON.fromJson(message.getPayload().toString(), Packet.class); - executorService.submit(() -> { - try { - var token = (String) session.getAttributes().get("token"); - SessionManager.setContext(token); - - var packet = GSON.fromJson(message.getPayload().toString(), Packet.class); - if (StringUtils.equals(packet.getType(), Packet.TYPE_CONNECT)) { - onConnectPacket(session, packet); - } else { - var console = SessionManager - .getCurrentSession() - .getConsoles() - .get(session.getId()); - - if (console != null) { - var db = TreeUtils.getValue(console.getNodeKey(), "database"); - SessionManager.getCurrentSession().getDatasource().getConnectionManager().setDatabaseContext(db); - - var handler = SessionManager.getCurrentSession().getConsoles().get(session.getId()); - handler.handle(packet); - } - } - } catch (Exception e) { - log.error("handle message error", e); + if (this.isQueryConsoleCancel(token, session.getId(), packet)) { + this.cancelQueryConsole(token, session.getId()); + return; + } + + this.sessionExecutors + .computeIfAbsent(session.getId(), ignored -> new SerialExecutor(this.executorService)) + .execute(() -> this.processMessage(session, token, packet)); + } + + private void processMessage(WebSocketSession session, String token, Packet packet) { + try { + SessionManager.setContext(token); + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null) { + return; + } + if (currentSession.getController() != null) { + currentSession.getController().bindDialogOwner(session.getId()); + } + if (StringUtils.equals(packet.getType(), Packet.TYPE_CONNECT)) { + onConnectPacket(session, packet); + return; + } + + var console = currentSession.getConsoles().get(session.getId()); + if (console != null) { + this.setDatabaseContext(console); + console.handle(packet); + } + } catch (Exception e) { + log.error("handle message error", e); + var serialExecutor = this.sessionExecutors.remove(session.getId()); + if (serialExecutor != null) { + serialExecutor.shutdown(); + } + this.closeConsole(token, session.getId()); + this.closeWebSocket(session); + } finally { + var currentSession = SessionManager.getSession(token); + if (currentSession != null && currentSession.getController() != null) { + currentSession.getController().clearDialogOwner(); } - }); + } + } + + private boolean isQueryConsoleCancel(String token, String sessionId, Packet packet) { + if (!StringUtils.equals(packet.getType(), Packet.TYPE_QUERY_CONSOLE_ACTION)) { + return false; + } + var action = GSON.fromJson(GSON.toJson(packet.getData()), QueryConsoleAction.class); + if (!StringUtils.equals(action.getAction(), QueryConsoleAction.ACTION_CANCEL)) { + return false; + } + var currentSession = SessionManager.getSession(token); + return currentSession != null && currentSession.getConsoles().get(sessionId) instanceof QueryConsole; + } + + private void cancelQueryConsole(String token, String sessionId) { + var currentSession = SessionManager.getSession(token); + if (currentSession == null) { + return; + } + var console = currentSession.getConsoles().get(sessionId); + if (console instanceof QueryConsole queryConsole) { + queryConsole.onCancel(); + } } private void onConnectPacket(WebSocketSession session, Packet packet) { Connect connect = GSON.fromJson(GSON.toJson(packet.getData()), Connect.class); var webSess = SessionManager.getCurrentSession(); - var node = this.resolveNode( - webSess.getDatasource().getResourceBrowser(), - connect.getNodeKey(), - connect.getType() - ); - if (node == null) { + ConsoleContext context; + try { + context = new ConsoleContextResolver(webSess.getDatasource().getResourceBrowser()) + .resolve(connect.getNodeKey(), connect.getType()); + } catch (ConsoleContextResolutionException e) { new PacketIO(session).sendPacket( "show_message", new Message(MessageLevel.ERROR, "Invalid console context") ); + this.closeSessionConsole(session); + this.closeWebSocket(session); return; } - connect.setNodeKey(node.getKey()); - Console console = switch (connect.getType()) { - case Connect.CONSOLE_TYPE_QUERY -> - new QueryConsole(webSess.getDatasource(), session, node.getKey()); - case Connect.CONSOLE_TYPE_DATA_VIEW -> - new DataViewConsole(webSess.getDatasource(), session, node.getKey()); - default -> null; - }; + connect.setNodeKey(context.nodeKey()); + Console console = this.createConsole(connect.getType(), webSess.getDatasource(), session, context); if (console != null) { - webSess.getConsoles().put(session.getId(), console); - - var db = TreeUtils.getValue(console.getNodeKey(), "database"); - SessionManager.getCurrentSession().getDatasource().getConnectionManager().setDatabaseContext(db); - + this.setDatabaseContext(console); + var token = (String) session.getAttributes().get("token"); + if (!SessionManager.registerConsole(token, session.getId(), console)) { + console.close(); + this.closeWebSocket(session); + return; + } console.onInit(connect); log.info("User {} open a console ", webSess.getUsername()); } } - TreeNode resolveNode(ResourceBrowser resourceBrowser, String nodeKey, String consoleType) { - if (resourceBrowser == null || StringUtils.isBlank(nodeKey) || StringUtils.isBlank(consoleType)) { - return null; - } - TreeNode node; - try { - var root = resourceBrowser.getTree(); - node = root == null ? null : TreeUtils.getNode(root, nodeKey); - } catch (SQLException e) { - return null; - } - if (node == null) { - return null; - } - var allowedTypes = switch (consoleType) { - case Connect.CONSOLE_TYPE_QUERY -> QUERY_NODE_TYPES; - case Connect.CONSOLE_TYPE_DATA_VIEW -> DATA_VIEW_NODE_TYPES; - default -> Set.of(); + protected Console createConsole(String type, org.jumpserver.chen.framework.datasource.Datasource datasource, + WebSocketSession session, ConsoleContext context) { + return switch (type) { + case Connect.CONSOLE_TYPE_QUERY -> new QueryConsole(datasource, session, context); + case Connect.CONSOLE_TYPE_CONSOLE -> new QueryConsole(datasource, session, context, true); + case Connect.CONSOLE_TYPE_DATA_VIEW -> new DataViewConsole(datasource, session, context); + default -> null; }; - return allowedTypes.contains(node.getType()) ? node : null; } + private void setDatabaseContext(Console console) { + var connectionManager = SessionManager.getCurrentSession().getDatasource().getConnectionManager(); + var db = console.getContext().database(); + if (StringUtils.isNotBlank(db)) { + connectionManager.setDatabaseContext(db); + } + } + + @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { log.error("websocket error", exception); + this.closeSessionConsole(session); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { + this.closeSessionConsole(session); + } + + private void closeSessionConsole(WebSocketSession session) { + var serialExecutor = this.sessionExecutors.remove(session.getId()); + if (serialExecutor != null) { + serialExecutor.shutdown(); + } var token = (String) session.getAttributes().get("token"); + this.closeConsole(token, session.getId()); + } + + private void closeConsole(String token, String sessionId) { SessionManager.setContext(token); - var sess = SessionManager.getCurrentSession(); - if (sess == null) { + var currentSession = SessionManager.getCurrentSession(); + if (currentSession == null) { return; } - Console console = SessionManager - .getCurrentSession() - .getConsoles() - .get(session.getId()); + if (currentSession.getController() != null) { + currentSession.getController().cancelDialogs(sessionId); + } + Console console = currentSession.getConsoles().remove(sessionId); if (console != null) { console.close(); } - SessionManager.getCurrentSession().getConsoles().remove(session.getId()); + } + + private void closeWebSocket(WebSocketSession session) { + try { + if (session.isOpen()) { + session.close(CloseStatus.SERVER_ERROR); + } + } catch (Exception e) { + log.warn("close websocket session failed, sessionId={}", session.getId(), e); + } + } + + private static final class SerialExecutor { + private final ExecutorService executor; + private final Queue tasks = new ArrayDeque<>(); + private Runnable active; + private boolean accepting = true; + + private SerialExecutor(ExecutorService executor) { + this.executor = executor; + } + + private synchronized void execute(Runnable task) { + if (!this.accepting) { + return; + } + this.tasks.offer(() -> { + try { + task.run(); + } finally { + this.scheduleNext(); + } + }); + if (this.active == null) { + this.scheduleNext(); + } + } + + private synchronized void scheduleNext() { + this.active = this.tasks.poll(); + if (this.active != null) { + this.executor.submit(this.active); + } + } + + private synchronized void shutdown() { + this.accepting = false; + this.tasks.clear(); + } } } diff --git a/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsFilterTest.java b/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsFilterTest.java new file mode 100644 index 0000000..701bdad --- /dev/null +++ b/backend/framework/src/test/java/org/jumpserver/chen/framework/utils/PageUtilsFilterTest.java @@ -0,0 +1,64 @@ +package org.jumpserver.chen.framework.utils; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PageUtilsFilterTest { + @Test + void addsFilterToTableQuery() { + String sql = PageUtils.filter( + "select * from `demo`.`users`", + DbType.mysql, + "status = 'active' AND id > 10" + ); + + String where = where(sql, DbType.mysql); + assertTrue(where.contains("status = 'active'")); + assertTrue(where.contains("id > 10")); + } + + @Test + void combinesWithExistingWhereClause() { + String sql = PageUtils.filter("select * from users where tenant_id = 7", DbType.mysql, "enabled = 1"); + + String where = where(sql, DbType.mysql); + assertTrue(where.contains("tenant_id = 7")); + assertTrue(where.contains("enabled = 1")); + } + + @Test + void acceptsDialectSpecificQuotedIdentifiers() { + String sql = PageUtils.filter("select * from \"public\".\"users\"", DbType.postgresql, "\"displayName\" IS NOT NULL"); + + assertTrue(where(sql, DbType.postgresql).contains("\"displayName\" IS NOT NULL")); + } + + @Test + void emptyFilterLeavesSqlUnchanged() { + String sql = "select * from users"; + assertEquals(sql, PageUtils.filter(sql, DbType.mysql, " ")); + } + + @Test + void rejectsAdditionalStatementsAndClauses() { + assertThrows(IllegalArgumentException.class, + () -> PageUtils.filter("select * from users", DbType.mysql, "1 = 1; delete from users")); + assertThrows(IllegalArgumentException.class, + () -> PageUtils.filter("select * from users", DbType.mysql, "1 = 1 order by id")); + assertThrows(IllegalArgumentException.class, + () -> PageUtils.filter("select * from users", DbType.mysql, "1 = 1 limit 1")); + } + + private String where(String sql, DbType dbType) { + SQLSelectStatement statement = (SQLSelectStatement) SQLUtils.parseSingleStatement(sql, dbType); + SQLSelectQueryBlock query = (SQLSelectQueryBlock) statement.getSelect().getQuery(); + return query.getWhere().toString(); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java index 55344a1..bb098e1 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickHouseDatasource.java @@ -4,6 +4,7 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; public class ClickHouseDatasource extends BaseDatasource { @@ -13,6 +14,7 @@ public class ClickHouseDatasource extends BaseDatasource { public ClickHouseDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new ClickhouseConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new ClickhouseMetadataProvider(this.connectionManager)); this.resourceBrowser = new ClickhouseResourceBrowser(this.connectionManager); this.actionHandler = new ClickhouseActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java index ce83593..e8a539e 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseActionHandler.java @@ -64,13 +64,6 @@ public EventEmitter onSchemaProperties(TreeNode node) throws SQLException { return this.onShowObjectProperties("schema", SQL_SELECT_SCHEMA_DETAIL, node); } - private static final String SQL_SELECT_TABLE_DETAIL = "select TABLE_NAME,TABLE_SCHEMA,TABLE_TYPE,ENGINE,AVG_ROW_LENGTH,DATA_LENGTH,MAX_DATA_LENGTH,CREATE_TIME,TABLE_COLLATION from information_schema.TABLES WHERE TABLE_NAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseMetadataProvider.java new file mode 100644 index 0000000..e9ccf07 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseMetadataProvider.java @@ -0,0 +1,180 @@ +package org.jumpserver.chen.modules.clickhouse; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class ClickhouseMetadataProvider extends BaseDatabaseMetadataProvider { + + public ClickhouseMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, false, false, true, true, + true, true, true, false, false, true, true + ); + + private static final String VIEW_ENGINES = "'View', 'MaterializedView', 'LiveView', 'WindowView'"; + + private static final String SQL_SCHEMAS = "SELECT name AS name FROM system.databases"; + + private static final String SQL_TABLES = """ + SELECT name, + engine, + NULL AS character_set, + NULL AS collation, + comment + FROM system.tables + WHERE database = ? AND is_temporary = 0 AND engine NOT IN (%s) + ORDER BY name + """.formatted(VIEW_ENGINES); + + private static final String SQL_TABLE_STATS = """ + SELECT name, + total_rows AS estimated_rows, + total_bytes AS total_size_bytes + FROM system.tables + WHERE database = ? AND is_temporary = 0 AND engine NOT IN (%s) + ORDER BY name + """.formatted(VIEW_ENGINES); + + private static final String SQL_VIEWS = """ + SELECT name, engine AS type, comment + FROM system.tables + WHERE database = ? AND is_temporary = 0 AND engine IN (%s) + ORDER BY name + """.formatted(VIEW_ENGINES); + + private static final String SQL_INDEXES = """ + SELECT name, + table AS table_name, + NULL AS column_name, + expr AS expression, + NULL AS is_unique, + type AS method + FROM system.data_skipping_indices + WHERE database = ? + ORDER BY table, name + """; + + private static final String SQL_COLUMNS = """ + SELECT name AS name, table AS table_name, type AS native_type, NULL AS nullable + FROM system.columns + WHERE database = ? AND table IN (__IN__) + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + stringValue(row, "engine"), + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW) || kinds.contains(RelationKind.MATERIALIZED_VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + var type = stringValue(row, "type"); + var kind = type != null && type.toUpperCase(Locale.ROOT).contains("MATERIALIZED") + ? RelationKind.MATERIALIZED_VIEW + : RelationKind.VIEW; + if (!kinds.contains(kind)) { + continue; + } + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), kind), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + @Override + public String getSchemaDefinition(RelationScope scope) throws SQLException { + var rows = query("SHOW CREATE DATABASE " + quoteIdentifier(scope.schema()), List.of()); + if (rows.isEmpty()) { + return null; + } + var values = rows.get(0).values().iterator(); + values.next(); + return values.hasNext() ? String.valueOf(values.next()) : null; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT name, engine, total_rows, total_bytes, comment + FROM system.tables + WHERE database = ? AND name = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + @Override + protected String quoteIdentifier(String value) { + return "`" + value.replace("`", "``") + "`"; + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java index 92427c4..e53f69a 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class ClickhouseResourceBrowser extends BaseResourceBrowser { public ClickhouseResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new ClickhouseSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "select schema_name as name from information_schema.schemata"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "select table_name from information_schema.tables where table_schema = '?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "select table_name from information_schema.views where table_schema = '?'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS NAME, COLUMN_TYPE AS TYPE, COLUMN_KEY AS `KEY`, IS_NULLABLE AS `NULLABLE`, COLUMN_DEFAULT AS `DEFAULT`, EXTRA AS EXTRA, COLUMN_COMMENT AS COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?' AND TABLE_NAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseSQLHintsHandler.java deleted file mode 100644 index 007f62e..0000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/clickhouse/ClickhouseSQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.clickhouse; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class ClickhouseSQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public ClickhouseSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "select column_name as name, table_schema,table_name from information_schema.columns where table_schema = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - private static final String SQL_GET_ALL_TABLES = "select table_name as name, table_schema from information_schema.tables where table_schema='?' "; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java index 34a8849..376e5c1 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMActionHandler.java @@ -58,13 +58,6 @@ public List getTableActions(TreeNode node) { ); } - private static final String SQL_SELECT_TABLE_DETAIL = "select TABNAME, TBSPACE, TABSCHEMA, TYPE, STATUS, COLCOUNT, ACTIVE_BLOCKS, AVGROWSIZE, OWNER, CREATE_TIME from syscat.TABLES WHERE TBSPACE is not null AND TABNAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java index ee517fe..f8a1bb0 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMDatasource.java @@ -4,6 +4,7 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; public class DMDatasource extends BaseDatasource { @@ -13,6 +14,7 @@ public class DMDatasource extends BaseDatasource { public DMDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new DMConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new DmMetadataProvider(this.connectionManager)); this.resourceBrowser = new DMResourceBrowser(this.connectionManager); this.actionHandler = new DMActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java index 5fa4d5d..c8a58c5 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class DMResourceBrowser extends BaseResourceBrowser { public DMResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new DMSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT NAME FROM SYSOBJECTS t WHERE t.TYPE$ ='SCH'"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "SELECT TABLE_NAME AS NAME FROM ALL_TABLES WHERE OWNER='?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT VIEW_NAME AS NAME FROM ALL_VIEWS WHERE OWNER='?'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS `NAME`,DATA_TYPE AS `TYPE`,DATA_LENGTH AS `LENGTH`,DATA_DEFAULT AS `DEFAULT` FROM ALL_TAB_COLUMNS WHERE OWNER='?' AND TABLE_NAME='?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMSQLHintsHandler.java deleted file mode 100644 index ee036e3..0000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DMSQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.dameng; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class DMSQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public DMSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME,OWNER FROM ALL_TABLES WHERE OWNER= '?'"; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "SELECT COLUMN_NAME,OWNER,TABLE_NAME FROM ALL_TAB_COLUMNS WHERE OWNER = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmMetadataProvider.java new file mode 100644 index 0000000..30cc54c --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/dameng/DmMetadataProvider.java @@ -0,0 +1,164 @@ +package org.jumpserver.chen.modules.dameng; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class DmMetadataProvider extends BaseDatabaseMetadataProvider { + + public DmMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, false, false, true, false, + true, false, false, false, false, true, true + ); + + private static final String SQL_SCHEMAS = "SELECT NAME AS name FROM SYSOBJECTS t WHERE t.TYPE$ ='SCH'"; + + private static final String SQL_TABLES = """ + SELECT t.table_name AS name, + NULL AS engine, + NULL AS character_set, + NULL AS collation, + c.comments AS comment + FROM all_tables t + LEFT JOIN all_tab_comments c + ON c.owner = t.owner AND c.table_name = t.table_name AND c.table_type = 'TABLE' + WHERE t.owner = ? + ORDER BY t.table_name + """; + + private static final String SQL_TABLE_STATS = """ + SELECT t.table_name AS name, + t.num_rows AS estimated_rows, + NULL AS total_size_bytes + FROM all_tables t + WHERE t.owner = ? + ORDER BY t.table_name + """; + + private static final String SQL_VIEWS = """ + SELECT v.view_name AS name, 'VIEW' AS type, c.comments AS comment + FROM all_views v + LEFT JOIN all_tab_comments c + ON c.owner = v.owner AND c.table_name = v.view_name AND c.table_type = 'VIEW' + WHERE v.owner = ? + ORDER BY v.view_name + """; + + private static final String SQL_INDEXES = """ + SELECT i.index_name AS name, + i.table_name AS table_name, + CASE WHEN e.column_expression IS NULL THEN c.column_name END AS column_name, + e.column_expression AS expression, + CASE WHEN i.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique, + i.index_type AS method + FROM all_indexes i + JOIN all_ind_columns c + ON c.index_owner = i.owner AND c.index_name = i.index_name AND c.table_owner = i.table_owner + LEFT JOIN all_ind_expressions e + ON e.index_owner = c.index_owner AND e.index_name = c.index_name + AND e.table_owner = c.table_owner AND e.column_position = c.column_position + WHERE i.owner = ? + ORDER BY i.table_name, i.index_name, c.column_position + """; + + private static final String SQL_COLUMNS = """ + SELECT COLUMN_NAME AS name, TABLE_NAME AS table_name, DATA_TYPE AS native_type, + NULLABLE AS nullable + FROM ALL_TAB_COLUMNS + WHERE OWNER = ? AND TABLE_NAME IN (__IN__) + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT table_name, tablespace_name, status, num_rows, blocks, avg_row_len, sample_size, owner + FROM all_tables + WHERE owner = ? AND table_name = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java index 84f8022..795b628 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ActionHandler.java @@ -64,13 +64,6 @@ public EventEmitter onSchemaProperties(TreeNode node) throws SQLException { return this.onShowObjectProperties("schema", SQL_SELECT_SCHEMA_DETAIL, node); } - private static final String SQL_SELECT_TABLE_DETAIL = "select TABNAME, TBSPACE, TABSCHEMA, TYPE, STATUS, COLCOUNT, ACTIVE_BLOCKS, AVGROWSIZE, OWNER, CREATE_TIME from syscat.TABLES WHERE TBSPACE is not null AND TABNAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java index 210603f..587d746 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2Datasource.java @@ -4,6 +4,7 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; public class DB2Datasource extends BaseDatasource { @@ -13,6 +14,7 @@ public class DB2Datasource extends BaseDatasource { public DB2Datasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new DB2ConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new DB2MetadataProvider(this.connectionManager)); this.resourceBrowser = new DB2ResourceBrowser(this.connectionManager); this.actionHandler = new DB2ActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2MetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2MetadataProvider.java new file mode 100644 index 0000000..ed9de91 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2MetadataProvider.java @@ -0,0 +1,164 @@ +package org.jumpserver.chen.modules.db2; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class DB2MetadataProvider extends BaseDatabaseMetadataProvider { + + public DB2MetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, false, false, true, false, + true, true, false, true, false, true, true + ); + + private static final String SQL_SCHEMAS = "SELECT SCHEMANAME AS name FROM syscat.schemata"; + + private static final String SQL_TABLES = """ + SELECT RTRIM(t.tabname) AS name, + NULL AS engine, + CHAR(t.codepage) AS character_set, + NULL AS collation, + t.remarks AS comment + FROM syscat.tables t + WHERE t.tabschema = ? AND t.type = 'T' + ORDER BY t.tabname + """; + + private static final String SQL_TABLE_STATS = """ + WITH index_pages AS ( + SELECT tabschema, tabname, SUM(COALESCE(nleaf, 0)) AS pages + FROM syscat.indexes + GROUP BY tabschema, tabname + ) + SELECT RTRIM(t.tabname) AS name, + t.card AS estimated_rows, + (COALESCE(t.npages, 0) + COALESCE(i.pages, 0)) * ts.pagesize AS total_size_bytes + FROM syscat.tables t + LEFT JOIN syscat.tablespaces ts ON ts.tbspaceid = t.tbspaceid + LEFT JOIN index_pages i ON i.tabschema = t.tabschema AND i.tabname = t.tabname + WHERE t.tabschema = ? AND t.type = 'T' + ORDER BY t.tabname + """; + + private static final String SQL_VIEWS = """ + SELECT RTRIM(tabname) AS name, 'VIEW' AS type, remarks AS comment + FROM syscat.tables + WHERE tabschema = ? AND type = 'V' + ORDER BY tabname + """; + + private static final String SQL_INDEXES = """ + SELECT RTRIM(i.indname) AS name, + RTRIM(i.tabname) AS table_name, + RTRIM(c.colname) AS column_name, + CASE WHEN i.uniquerule IN ('P', 'U') THEN 1 ELSE 0 END AS is_unique, + i.indextype AS method, + NULL AS definition + FROM syscat.indexes i + LEFT JOIN syscat.indexcoluse c + ON c.indschema = i.indschema AND c.indname = i.indname + WHERE i.tabschema = ? + ORDER BY i.tabname, i.indname, c.colseq + """; + + private static final String SQL_COLUMNS = """ + SELECT COLNAME AS name, TABNAME AS table_name, TYPENAME AS native_type, + NULLS AS nullable + FROM syscat.COLUMNS + WHERE TABSCHEMA = ? AND TABNAME IN (__IN__) + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + stringValue(row, "character_set"), + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT TABNAME, TBSPACE, TABSCHEMA, TYPE, STATUS, COLCOUNT, ACTIVE_BLOCKS, AVGROWSIZE, OWNER, CREATE_TIME + FROM syscat.TABLES + WHERE TABSCHEMA = ? AND TABNAME = ? AND TBSPACE IS NOT NULL + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java index 11d497b..c0ddc42 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2ResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class DB2ResourceBrowser extends BaseResourceBrowser { public DB2ResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new DB2SQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "select SCHEMANAME AS NAME from syscat.schemata"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "select TABNAME from syscat.TABLES where TABSCHEMA = '?' and TYPE = 'T'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "select TABNAME from syscat.TABLES where TABSCHEMA = '?' and TYPE = 'V'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "select COLNAME, TYPENAME, LENGTH, DEFAULT, REMARKS from syscat.COLUMNS where TABSCHEMA = '?' and TABNAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2SQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2SQLHintsHandler.java deleted file mode 100644 index 2f00b67..0000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/db2/DB2SQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.db2; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class DB2SQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public DB2SQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "select COLNAME, TABSCHEMA, TABNAME from syscat.COLUMNS where TABSCHEMA = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - private static final String SQL_GET_ALL_TABLES = "select TABNAME, TABSCHEMA from syscat.TABLES where TABSCHEMA = '?'"; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java index fa70990..382aa70 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBConnectionManager.java @@ -1,5 +1,6 @@ package org.jumpserver.chen.modules.mariadb; +import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.Datasource; import org.jumpserver.chen.framework.datasource.base.BaseConnectionManager; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; @@ -46,9 +47,18 @@ public String getDisplayJDBCUrl() { return this.getConnectInfo().toDisplayJDBCUrl(jdbcUrlTemplate); } + @Override + public String getDatabaseContextKey() { + // MariaDB 的对象树使用 schema 节点表示 database,连接池默认库也要取这个值。 + return "schema"; + } @Override public String getJDBCUrl(String database) { - return this.jdbcUrl; + if (StringUtils.isBlank(database)) { + return this.jdbcUrl; + } + // 连接池不能依赖上一条连接执行过 USE,这里显式把 database 放进 JDBC URL。 + return this.getConnectInfo().toJDBCUrl(jdbcUrlTemplate, database); } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java index 7eb034d..a99043b 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mariadb/MariaDBDatasource.java @@ -4,6 +4,8 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.modules.mysql.MysqlMetadataProvider; import org.jumpserver.chen.modules.mysql.MysqlActionHandler; public class MariaDBDatasource extends BaseDatasource { @@ -14,6 +16,7 @@ public class MariaDBDatasource extends BaseDatasource { public MariaDBDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new MariaDBConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new MysqlMetadataProvider(this.connectionManager)); this.resourceBrowser = new MariaDBResourceBrowser(this.connectionManager); this.actionHandler = new MysqlActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java index 0109e6d..805df5b 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlActionHandler.java @@ -64,13 +64,6 @@ public EventEmitter onSchemaProperties(TreeNode node) throws SQLException { return this.onShowObjectProperties("schema", SQL_SELECT_SCHEMA_DETAIL, node); } - private static final String SQL_SELECT_TABLE_DETAIL = "select TABLE_NAME,TABLE_SCHEMA,TABLE_TYPE,ENGINE,AVG_ROW_LENGTH,DATA_LENGTH,MAX_DATA_LENGTH,CREATE_TIME,TABLE_COLLATION from information_schema.TABLES WHERE TABLE_NAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java index cc83f29..20c5e4d 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlConnectionManager.java @@ -108,8 +108,18 @@ public String getDisplayJDBCUrl() { return this.getConnectInfo().toDisplayJDBCUrl(jdbcUrlTemplate); } + @Override + public String getDatabaseContextKey() { + // MySQL 的对象树使用 schema 节点表示 database,连接池默认库也要取这个值。 + return "schema"; + } + @Override public String getJDBCUrl(String database) { - return this.jdbcUrl; + if (StringUtils.isBlank(database)) { + return this.jdbcUrl; + } + // 连接池不能依赖上一条连接执行过 USE,这里显式把 database 放进 JDBC URL。 + return this.getConnectInfo().toJDBCUrl(jdbcUrlTemplate, database); } } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java index 5d5fdd2..693baf3 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlDatasource.java @@ -4,6 +4,7 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; public class MysqlDatasource extends BaseDatasource { @@ -13,6 +14,7 @@ public class MysqlDatasource extends BaseDatasource { public MysqlDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new MysqlConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new MysqlMetadataProvider(this.connectionManager)); this.resourceBrowser = new MysqlResourceBrowser(this.connectionManager); this.actionHandler = new MysqlActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlMetadataProvider.java new file mode 100644 index 0000000..d54c09c --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlMetadataProvider.java @@ -0,0 +1,173 @@ +package org.jumpserver.chen.modules.mysql; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class MysqlMetadataProvider extends BaseDatabaseMetadataProvider { + + public MysqlMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, false, false, true, true, + true, true, true, true, true, true, false + ); + + private static final String SQL_SCHEMAS = "SELECT SCHEMA_NAME AS name FROM INFORMATION_SCHEMA.SCHEMATA"; + + private static final String SQL_TABLES = """ + SELECT t.TABLE_NAME AS name, + t.ENGINE AS engine, + c.CHARACTER_SET_NAME AS character_set, + t.TABLE_COLLATION AS collation, + t.TABLE_COMMENT AS comment + FROM INFORMATION_SCHEMA.TABLES t + LEFT JOIN INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY c + ON c.COLLATION_NAME = t.TABLE_COLLATION + WHERE t.TABLE_SCHEMA = ? AND t.TABLE_TYPE <> 'VIEW' + ORDER BY t.TABLE_NAME + """; + + private static final String SQL_TABLE_STATS = """ + SELECT TABLE_NAME AS name, + TABLE_ROWS AS estimated_rows, + COALESCE(DATA_LENGTH, 0) + COALESCE(INDEX_LENGTH, 0) AS total_size_bytes + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = ? AND TABLE_TYPE <> 'VIEW' + """; + + private static final String SQL_VIEWS = """ + SELECT TABLE_NAME AS name, 'VIEW' AS type, NULL AS comment + FROM INFORMATION_SCHEMA.VIEWS + WHERE TABLE_SCHEMA = ? + ORDER BY TABLE_NAME + """; + + private static final String SQL_INDEXES = """ + SELECT INDEX_NAME AS name, + TABLE_NAME AS table_name, + COLUMN_NAME AS column_name, + CASE WHEN NON_UNIQUE = 0 THEN 1 ELSE 0 END AS is_unique, + INDEX_TYPE AS method, + NULL AS definition + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = ? + ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX + """; + + private static final String SQL_COLUMNS = """ + SELECT COLUMN_NAME AS name, TABLE_NAME AS table_name, COLUMN_TYPE AS native_type, + IS_NULLABLE AS nullable + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (__IN__) + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + stringValue(row, "engine"), + stringValue(row, "character_set"), + stringValue(row, "collation") + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + @Override + public String getSchemaDefinition(RelationScope scope) throws SQLException { + var rows = query("SHOW CREATE DATABASE " + quoteIdentifier(scope.schema()), List.of()); + if (rows.isEmpty()) { + return null; + } + var values = rows.get(0).values().iterator(); + values.next(); + return values.hasNext() ? String.valueOf(values.next()) : null; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT TABLE_NAME, TABLE_SCHEMA, TABLE_TYPE, ENGINE, AVG_ROW_LENGTH, DATA_LENGTH, + MAX_DATA_LENGTH, CREATE_TIME, TABLE_COLLATION + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } + + @Override + protected String quoteIdentifier(String value) { + return "`" + value.replace("`", "``") + "`"; + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java index e6803c8..965a988 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlResourceBrowser.java @@ -2,49 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class MysqlResourceBrowser extends BaseResourceBrowser { public MysqlResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new MysqlSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT SCHEMA_NAME AS NAME FROM INFORMATION_SCHEMA.SCHEMATA"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); + super(connectionManager); } - - private static final String SQL_GET_TABLES = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE != 'VIEW'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE = 'VIEW'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS NAME, COLUMN_TYPE AS TYPE, COLUMN_KEY AS `KEY`, IS_NULLABLE AS `NULLABLE`, COLUMN_DEFAULT AS `DEFAULT`, EXTRA AS EXTRA, COLUMN_COMMENT AS COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?' AND TABLE_NAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlSQLHintsHandler.java deleted file mode 100644 index bb4429a..0000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/mysql/MysqlSQLHintsHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.jumpserver.chen.modules.mysql; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class MysqlSQLHintsHandler extends BaseSQLHintsHandler { - private final ConnectionManager connectionManager; - - public MysqlSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - private static final String SQL_GET_SCHEMA_ALL_FIELDS = "SELECT COLUMN_NAME AS NAME, TABLE_SCHEMA,TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?'"; - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_SCHEMA_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME AS NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='?' "; - - public List
getALlTables(String context) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, context) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java index e3c8d85..cd24f19 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleActionHandler.java @@ -35,12 +35,6 @@ public List getTableActions(TreeNode node) { ); } - private static final String SQL_SELECT_TABLE_DETAIL = "SELECT TABLE_NAME,TABLESPACE_NAME,STATUS,NUM_ROWS,BLOCKS,AVG_ROW_LEN,SAMPLE_SIZE,OWNER FROM ALL_TABLES WHERE TABLESPACE_NAME is not null AND TABLE_NAME = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java index cdf6d6b..82de8a5 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleDatasource.java @@ -4,6 +4,7 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; public class OracleDatasource extends BaseDatasource { @@ -13,6 +14,7 @@ public class OracleDatasource extends BaseDatasource { public OracleDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new OracleConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new OracleMetadataProvider(this.connectionManager)); this.resourceBrowser = new OracleResourceBrowser(this.connectionManager); this.actionHandler = new OracleActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleMetadataProvider.java new file mode 100644 index 0000000..2273db5 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleMetadataProvider.java @@ -0,0 +1,185 @@ +package org.jumpserver.chen.modules.oracle; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class OracleMetadataProvider extends BaseDatabaseMetadataProvider { + + public OracleMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + false, true, true, true, true, false, false, true, false, + true, true, false, false, false, true, true + ); + + private static final String SQL_SCHEMAS = "SELECT USERNAME AS name FROM ALL_USERS"; + + private static final String SQL_TABLES = """ + SELECT t.table_name AS name, + NULL AS engine, + NULL AS character_set, + NULL AS collation, + c.comments AS comment + FROM all_tables t + LEFT JOIN all_tab_comments c + ON c.owner = t.owner AND c.table_name = t.table_name AND c.table_type = 'TABLE' + WHERE t.owner = ? AND t.tablespace_name IS NOT NULL + ORDER BY t.table_name + """; + + private static final String SQL_TABLE_STATS = """ + WITH target_schema AS (SELECT ? AS owner FROM dual), object_segments AS ( + SELECT s.segment_name AS table_name, s.bytes + FROM all_segments s JOIN target_schema x ON x.owner = s.owner + WHERE s.segment_type IN ('TABLE', 'TABLE PARTITION', 'TABLE SUBPARTITION') + UNION ALL + SELECT i.table_name, s.bytes + FROM all_segments s + JOIN all_indexes i ON i.owner = s.owner AND i.index_name = s.segment_name + JOIN target_schema x ON x.owner = i.owner + WHERE s.segment_type IN ('INDEX', 'INDEX PARTITION', 'INDEX SUBPARTITION') + UNION ALL + SELECT l.table_name, s.bytes + FROM all_segments s + JOIN all_lobs l ON l.owner = s.owner AND s.segment_name IN (l.segment_name, l.index_name) + JOIN target_schema x ON x.owner = l.owner + ), object_sizes AS ( + SELECT table_name, SUM(bytes) AS total_size_bytes + FROM object_segments GROUP BY table_name + ) + SELECT t.table_name AS name, + t.num_rows AS estimated_rows, + z.total_size_bytes AS total_size_bytes + FROM all_tables t + JOIN target_schema x ON x.owner = t.owner + LEFT JOIN object_sizes z ON z.table_name = t.table_name + WHERE t.tablespace_name IS NOT NULL + ORDER BY t.table_name + """; + + private static final String SQL_VIEWS = """ + SELECT v.view_name AS name, 'VIEW' AS type, c.comments AS comment + FROM all_views v + LEFT JOIN all_tab_comments c + ON c.owner = v.owner AND c.table_name = v.view_name AND c.table_type = 'VIEW' + WHERE v.owner = ? + ORDER BY v.view_name + """; + + private static final String SQL_INDEXES = """ + SELECT i.index_name AS name, + i.table_name AS table_name, + CASE WHEN e.column_expression IS NULL THEN c.column_name END AS column_name, + e.column_expression AS expression, + CASE WHEN i.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique, + i.index_type AS method + FROM all_indexes i + JOIN all_ind_columns c + ON c.index_owner = i.owner AND c.index_name = i.index_name AND c.table_owner = i.table_owner + LEFT JOIN all_ind_expressions e + ON e.index_owner = c.index_owner AND e.index_name = c.index_name + AND e.table_owner = c.table_owner AND e.column_position = c.column_position + WHERE i.owner = ? + ORDER BY i.table_name, i.index_name, c.column_position + """; + + private static final String SQL_COLUMNS = """ + SELECT COLUMN_NAME AS name, TABLE_NAME AS table_name, DATA_TYPE AS native_type, + NULLABLE AS nullable + FROM ALL_TAB_COLUMNS + WHERE OWNER = ? AND TABLE_NAME IN (__IN__) + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(null, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT TABLE_NAME, TABLESPACE_NAME, STATUS, NUM_ROWS, BLOCKS, AVG_ROW_LEN, SAMPLE_SIZE, OWNER + FROM ALL_TABLES + WHERE OWNER = ? AND TABLE_NAME = ? AND TABLESPACE_NAME IS NOT NULL + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java index 26c0c47..36e1952 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleResourceBrowser.java @@ -2,48 +2,9 @@ import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.entity.resource.View; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; - public class OracleResourceBrowser extends BaseResourceBrowser { public OracleResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new OracleSQLHintsHandler(connectionManager)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT USERNAME FROM ALL_USERS"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); - } - - private static final String SQL_GET_TABLES = "SELECT TABLE_NAME FROM ALL_TABLES where TABLESPACE_NAME is not null AND OWNER='?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); + super(connectionManager); } - - private static final String SQL_GET_VIEWS = "SELECT VIEW_NAME FROM ALL_VIEWS WHERE OWNER= '?'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,CHAR_LENGTH,CHAR_USED,DATA_DEFAULT,COMMENTS FROM ALL_TAB_COLUMNS WHERE OWNER='?' AND TABLE_NAME='?'" ; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); - } - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleSQLHintsHandler.java deleted file mode 100644 index 14091df..0000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/oracle/OracleSQLHintsHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.jumpserver.chen.modules.oracle; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class OracleSQLHintsHandler extends BaseSQLHintsHandler { - - private final ConnectionManager connectionManager; - - - public OracleSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME,OWNER FROM ALL_TABLES WHERE OWNER= '?'"; - - public List
getALlTables(String schema) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, schema) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String SQL_GET_ALL_FIELDS = "SELECT COLUMN_NAME,OWNER,TABLE_NAME FROM ALL_TAB_COLUMNS WHERE OWNER = '?'"; - - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java index b5e6b02..d2f4d8b 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlActionHandler.java @@ -56,14 +56,6 @@ public EventEmitter onDatabaseProperties(TreeNode node) throws SQLException { return this.onShowObjectProperties("database", SQL_SELECT_DATABASE_DETAIL, node); } - - private static final String SQL_SELECT_TABLE_DETAIL = "SELECT table_name,table_schema,table_type FROM information_schema.tables WHERE table_name = '?'"; - - public EventEmitter onTableProperties(TreeNode node) throws SQLException { - return this.onShowObjectProperties("table", SQL_SELECT_TABLE_DETAIL, node); - } - - @Override public EventEmitter handleForm(FormData formData) throws SQLException { return null; diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java index 07fa880..34fc872 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlDatasource.java @@ -4,6 +4,7 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; public class PostgresqlDatasource extends BaseDatasource { @@ -13,6 +14,7 @@ public class PostgresqlDatasource extends BaseDatasource { public PostgresqlDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new PostgresqlConnectionManager(dbConnectInfo, this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new PostgresqlMetadataProvider(this.connectionManager)); this.resourceBrowser = new PostgresqlResourceBrowser(this.connectionManager); this.actionHandler = new PostgresqlActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlMetadataProvider.java new file mode 100644 index 0000000..ff1be3b --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlMetadataProvider.java @@ -0,0 +1,186 @@ +package org.jumpserver.chen.modules.postgresql; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.CatalogMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectProperties; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +public class PostgresqlMetadataProvider extends BaseDatabaseMetadataProvider { + + public PostgresqlMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + true, true, true, true, true, false, false, true, false, + true, true, false, false, false, true, true + ); + + private static final String SQL_CATALOGS = "SELECT DATNAME AS name FROM PG_DATABASE WHERE DATISTEMPLATE = FALSE"; + + private static final String SQL_SCHEMAS = "SELECT SCHEMA_NAME AS name FROM INFORMATION_SCHEMA.SCHEMATA"; + + private static final String SQL_TABLES = """ + SELECT c.relname AS name, + NULL::text AS character_set, + NULL::text AS collation, + obj_description(c.oid, 'pg_class') AS comment + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = ? AND c.relkind IN ('r', 'p') + ORDER BY c.relname + """; + + private static final String SQL_TABLE_STATS = """ + SELECT c.relname AS name, + c.reltuples::bigint AS estimated_rows, + pg_total_relation_size(c.oid) AS total_size_bytes + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = ? AND c.relkind IN ('r', 'p') + ORDER BY c.relname + """; + + private static final String SQL_VIEWS = """ + SELECT c.relname AS name, + CASE c.relkind WHEN 'm' THEN 'MATERIALIZED VIEW' ELSE 'VIEW' END AS type, + obj_description(c.oid, 'pg_class') AS comment + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = ? AND c.relkind IN ('v', 'm') + ORDER BY c.relname + """; + + private static final String SQL_INDEXES = """ + SELECT ic.relname AS name, + tc.relname AS table_name, + a.attname AS column_name, + CASE WHEN k.attnum = 0 THEN pg_get_indexdef(ic.oid, k.position, true) END AS expression, + i.indisunique AS is_unique, + am.amname AS method, + pg_get_indexdef(ic.oid) AS definition + FROM pg_index i + JOIN pg_class ic ON ic.oid = i.indexrelid + JOIN pg_class tc ON tc.oid = i.indrelid + JOIN pg_namespace n ON n.oid = tc.relnamespace + JOIN pg_am am ON am.oid = ic.relam + LEFT JOIN LATERAL unnest(i.indkey) WITH ORDINALITY k(attnum, position) ON true + LEFT JOIN pg_attribute a ON a.attrelid = tc.oid AND a.attnum = k.attnum AND k.attnum > 0 + WHERE n.nspname = ? + ORDER BY tc.relname, ic.relname, k.position + """; + + private static final String SQL_COLUMNS = """ + SELECT COLUMN_NAME AS name, TABLE_NAME AS table_name, DATA_TYPE AS native_type, + IS_NULLABLE AS nullable + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME IN (__IN__) + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listCatalogs() throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_CATALOGS, List.of())) { + result.add(new CatalogMetadata(stringValue(row, "name"))); + } + return result; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(catalog, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW) || kinds.contains(RelationKind.MATERIALIZED_VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + var type = stringValue(row, "type"); + var kind = type != null && type.toUpperCase(Locale.ROOT).contains("MATERIALIZED") + ? RelationKind.MATERIALIZED_VIEW + : RelationKind.VIEW; + if (!kinds.contains(kind)) { + continue; + } + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), kind), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } + + private static final String SQL_TABLE_PROPERTIES = """ + SELECT table_name, table_schema, table_type + FROM information_schema.tables + WHERE table_schema = ? AND table_name = ? + """; + + @Override + public ObjectProperties objectProperties(ObjectRef ref) throws SQLException { + return loadObjectProperties(ref, SQL_TABLE_PROPERTIES); + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java index fb51096..c1791f3 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlResourceBrowser.java @@ -1,87 +1,43 @@ package org.jumpserver.chen.modules.postgresql; -import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.*; -import org.jumpserver.chen.framework.datasource.sql.SQL; +import org.jumpserver.chen.framework.datasource.entity.resource.Schema; +import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.utils.TreeUtils; import org.jumpserver.chen.modules.postgresql.entity.Database; import java.sql.SQLException; import java.util.List; -import java.util.Map; - public class PostgresqlResourceBrowser extends BaseResourceBrowser { public PostgresqlResourceBrowser(ConnectionManager connectionManager) { - super(connectionManager, new PostgresqlSQLHintsHandler(connectionManager)); + super(connectionManager); } @Override public List getDatasourceChildNodes(TreeNode parent) throws SQLException { - return this.getDatabases() - .stream() - .map(database -> database.toResourceNode(parent)) - .toList(); - } - - public List getChildNodes(TreeNode node) throws SQLException { - if (node != null) { - var dbName = TreeUtils.getValue(node.getKey(), "database"); - if (StringUtils.isNotEmpty(dbName)) { - this.getConnectionManager().setDatabaseContext(dbName); - } - } - return super.getChildNodes(node); + return this.getDatabases().stream().map(database -> database.toResourceNode(parent)).toList(); } public List getDatabaseChildNodes(TreeNode node) throws SQLException { - return this.getSchemas() - .stream() - .map(schema -> schema.toResourceNode(node)) + var dbName = TreeUtils.getValue(node.getKey(), "database"); + return this.metadataCatalog().listSchemas(dbName).stream() + .map(schema -> { + var entity = new Schema(); + entity.setName(schema.name()); + return entity.toResourceNode(node); + }) .toList(); } - private static final String SQL_GET_DATABASES = "SELECT DATNAME AS NAME FROM PG_DATABASE WHERE DATISTEMPLATE = FALSE"; - public List getDatabases() throws SQLException { - return this.getDatabases(SQL.of(SQL_GET_DATABASES)); - } - - public List getDatabases(SQL sql) throws SQLException { - return this.getConnectionManager() - .getSqlActuator() - .getObjects(sql.getSql(), Database.class, Map.of("name", 1)); - } - - private static final String SQL_GET_SCHEMAS = "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); - } - - private static final String SQL_GET_TABLES = " SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE = 'BASE TABLE'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?' AND TABLE_TYPE = 'VIEW'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT COLUMN_NAME AS NAME, COLUMN_TYPE AS TYPE, COLUMN_KEY AS `KEY`, IS_NULLABLE AS `NULLABLE`, COLUMN_DEFAULT AS `DEFAULT`, EXTRA AS EXTRA, COLUMN_COMMENT AS COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '?' AND TABLE_NAME = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); + return this.metadataCatalog().listCatalogs().stream() + .map(catalog -> { + var database = new Database(); + database.setName(catalog.name()); + return database; + }) + .toList(); } - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlSQLHintsHandler.java deleted file mode 100644 index c0b970d..0000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/postgresql/PostgresqlSQLHintsHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.jumpserver.chen.modules.postgresql; - -import org.apache.commons.lang3.StringUtils; -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; -import org.jumpserver.chen.framework.utils.SqlIdentifierUtils; -import org.jumpserver.chen.framework.utils.TreeUtils; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class PostgresqlSQLHintsHandler extends BaseSQLHintsHandler { - - private final ConnectionManager connectionManager; - - - public PostgresqlSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - - private static final String SQL_GET_ALL_TABLES = "SELECT TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '?'"; - - public List
getALlTables(String schema) throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_TABLES, schema) - .getSql(), Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String SQL_GET_ALL_FIELDS = "SELECT COLUMN_NAME,TABLE_SCHEMA,TABLE_NAME AS NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA= '?'"; - - - public List getAllFields(String schema) throws SQLException { - return this.connectionManager - .getSqlActuator() - .getObjects(SQL.of(SQL_GET_ALL_FIELDS, schema).getSql(), Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - - } - - - @Override - public Map> getHints(String nodeKey, String context) throws SQLException { - - var db = TreeUtils.getValue(nodeKey, "database"); - if (StringUtils.isNotEmpty(db)) { - // nodeKey is client-controlled; reject URL metacharacters before it - // reaches the JDBC URL via setDatabaseContext. - SqlIdentifierUtils.validateDatabaseName(db); - this.connectionManager.setDatabaseContext(db); - } - - return super.getHints(nodeKey, context); - } - -} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java index 50a7c85..c36738b 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerDatasource.java @@ -4,6 +4,7 @@ import org.jumpserver.chen.framework.datasource.DatasourceFactory; import org.jumpserver.chen.framework.datasource.base.BaseDatasource; import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; public class SQLServerDatasource extends BaseDatasource { @@ -13,6 +14,7 @@ public class SQLServerDatasource extends BaseDatasource { public SQLServerDatasource(DBConnectInfo dbConnectInfo) { this.connectionManager = new SQLServerConnectionManager(dbConnectInfo,this); + this.metadataCatalog = new MetadataCatalog(this.connectionManager, new SQLServerMetadataProvider(this.connectionManager)); this.resourceBrowser = new SQLServerResourceBrowser(this.connectionManager); this.actionHandler = new SQLServerActionHandler(); } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerMetadataProvider.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerMetadataProvider.java new file mode 100644 index 0000000..e4c3fd7 --- /dev/null +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerMetadataProvider.java @@ -0,0 +1,184 @@ +package org.jumpserver.chen.modules.sqlserver; + +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.metadata.BaseDatabaseMetadataProvider; +import org.jumpserver.chen.framework.datasource.metadata.CatalogMetadata; +import org.jumpserver.chen.framework.datasource.metadata.ColumnMetadata; +import org.jumpserver.chen.framework.datasource.metadata.IndexMetadata; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class SQLServerMetadataProvider extends BaseDatabaseMetadataProvider { + + public SQLServerMetadataProvider(ConnectionManager connectionManager) { + super(connectionManager); + } + + private static final MetadataCapabilities CAPABILITIES = new MetadataCapabilities( + true, true, true, true, true, false, false, true, false, + true, true, false, false, false, true, true + ); + + private static final String SQL_CATALOGS = "SELECT name AS name FROM sys.databases"; + + private static final String SQL_SCHEMAS = "SELECT schema_name AS name FROM INFORMATION_SCHEMA.SCHEMATA"; + + private static final String SQL_TABLES = """ + SELECT t.name AS name, + NULL AS engine, + NULL AS character_set, + NULL AS collation, + CONVERT(nvarchar(max), ep.value) AS comment + FROM sys.tables t + JOIN sys.schemas s ON s.schema_id = t.schema_id + LEFT JOIN sys.extended_properties ep + ON ep.major_id = t.object_id AND ep.minor_id = 0 AND ep.name = 'MS_Description' + WHERE s.name = ? + ORDER BY t.name + """; + + private static final String SQL_TABLE_STATS = """ + WITH row_counts AS ( + SELECT object_id, SUM(rows) AS estimated_rows + FROM sys.partitions + WHERE index_id IN (0, 1) + GROUP BY object_id + ), allocated_sizes AS ( + SELECT p.object_id, SUM(CONVERT(bigint, a.total_pages)) * 8192 AS total_size_bytes + FROM sys.partitions p + JOIN sys.allocation_units a ON a.container_id IN (p.hobt_id, p.partition_id) + GROUP BY p.object_id + ) + SELECT t.name AS name, + r.estimated_rows AS estimated_rows, + z.total_size_bytes AS total_size_bytes + FROM sys.tables t + JOIN sys.schemas s ON s.schema_id = t.schema_id + LEFT JOIN row_counts r ON r.object_id = t.object_id + LEFT JOIN allocated_sizes z ON z.object_id = t.object_id + WHERE s.name = ? + ORDER BY t.name + """; + + private static final String SQL_VIEWS = """ + SELECT v.name AS name, + 'VIEW' AS type, + CONVERT(nvarchar(max), ep.value) AS comment + FROM sys.views v + JOIN sys.schemas s ON s.schema_id = v.schema_id + LEFT JOIN sys.extended_properties ep + ON ep.major_id = v.object_id AND ep.minor_id = 0 AND ep.name = 'MS_Description' + WHERE s.name = ? + ORDER BY v.name + """; + + private static final String SQL_INDEXES = """ + SELECT i.name AS name, + t.name AS table_name, + c.name AS column_name, + i.is_unique AS is_unique, + i.type_desc AS method, + NULL AS definition, + ic.is_included_column AS included + FROM sys.indexes i + JOIN sys.tables t ON t.object_id = i.object_id + JOIN sys.schemas s ON s.schema_id = t.schema_id + LEFT JOIN sys.index_columns ic + ON ic.object_id = i.object_id AND ic.index_id = i.index_id + LEFT JOIN sys.columns c + ON c.object_id = ic.object_id AND c.column_id = ic.column_id + WHERE s.name = ? AND i.name IS NOT NULL AND i.is_hypothetical = 0 + ORDER BY t.name, i.name, ic.is_included_column, ic.key_ordinal, ic.index_column_id + """; + + private static final String SQL_COLUMNS = """ + SELECT column_name AS name, table_name AS table_name, data_type AS native_type, + is_nullable AS nullable + FROM information_schema.columns + WHERE table_schema = ? AND table_name IN (__IN__) + """; + + @Override + public MetadataCapabilities capabilities() { + return CAPABILITIES; + } + + @Override + public List listCatalogs() throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_CATALOGS, List.of())) { + result.add(new CatalogMetadata(stringValue(row, "name"))); + } + return result; + } + + @Override + public List listSchemas(String catalog) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_SCHEMAS, List.of())) { + result.add(new SchemaMetadata(catalog, stringValue(row, "name"))); + } + return result; + } + + @Override + public List listRelations(RelationScope scope, Set kinds) throws SQLException { + var result = new ArrayList(); + if (kinds.contains(RelationKind.TABLE)) { + for (var row : query(SQL_TABLES, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + if (kinds.contains(RelationKind.VIEW)) { + for (var row : query(SQL_VIEWS, List.of(scope.schema()))) { + result.add(new RelationMetadata( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.VIEW), + stringValue(row, "comment"), + null, + null, + null + )); + } + } + return result; + } + + @Override + public List listColumns(List relations) throws SQLException { + return loadColumns(SQL_COLUMNS, relations); + } + + @Override + public List listIndexes(RelationScope scope) throws SQLException { + return groupIndexRows(query(SQL_INDEXES, List.of(scope.schema())), scope); + } + + @Override + public List listStatistics(RelationScope scope) throws SQLException { + var result = new ArrayList(); + for (var row : query(SQL_TABLE_STATS, List.of(scope.schema()))) { + result.add(new ObjectStatistics( + new ObjectRef(scope.catalog(), scope.schema(), stringValue(row, "name"), RelationKind.TABLE), + longValue(row, "estimated_rows"), + longValue(row, "total_size_bytes") + )); + } + return result; + } +} diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java index e4888bc..986fb27 100644 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java +++ b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerResourceBrowser.java @@ -1,89 +1,43 @@ package org.jumpserver.chen.modules.sqlserver; -import org.apache.commons.lang3.StringUtils; import org.jumpserver.chen.framework.datasource.ConnectionManager; import org.jumpserver.chen.framework.datasource.base.BaseResourceBrowser; -import org.jumpserver.chen.framework.datasource.entity.resource.*; -import org.jumpserver.chen.framework.datasource.sql.SQL; +import org.jumpserver.chen.framework.datasource.entity.resource.Schema; +import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; import org.jumpserver.chen.framework.utils.TreeUtils; import org.jumpserver.chen.modules.postgresql.entity.Database; import java.sql.SQLException; import java.util.List; -import java.util.Map; - public class SQLServerResourceBrowser extends BaseResourceBrowser { public SQLServerResourceBrowser(ConnectionManager connectionManager) { - - super(connectionManager, new SQLServerSQLHintsHandler(connectionManager)); + super(connectionManager); } @Override public List getDatasourceChildNodes(TreeNode parent) throws SQLException { - return this.getDatabases() - .stream() - .map(database -> database.toResourceNode(parent)) - .toList(); - } - - public List getChildNodes(TreeNode node) throws SQLException { - if (node != null) { - var dbName = TreeUtils.getValue(node.getKey(), "database"); - if (StringUtils.isNotEmpty(dbName)) { - this.getConnectionManager().setDatabaseContext(dbName); - } - } - return super.getChildNodes(node); + return this.getDatabases().stream().map(database -> database.toResourceNode(parent)).toList(); } public List getDatabaseChildNodes(TreeNode node) throws SQLException { - return this.getSchemas() - .stream() - .map(schema -> schema.toResourceNode(node)) + var dbName = TreeUtils.getValue(node.getKey(), "database"); + return this.metadataCatalog().listSchemas(dbName).stream() + .map(schema -> { + var entity = new Schema(); + entity.setName(schema.name()); + return entity.toResourceNode(node); + }) .toList(); } - private static final String SQL_GET_DATABASES = "SELECT name FROM sys.databases;"; - public List getDatabases() throws SQLException { - return this.getDatabases(SQL.of(SQL_GET_DATABASES)); - } - - public List getDatabases(SQL sql) throws SQLException { - return this.getConnectionManager() - .getSqlActuator() - .getObjects(sql.getSql(), Database.class, Map.of("name", 1)); - } - - // 查询当前数据库下所有的schema - private static final String SQL_GET_SCHEMAS = "SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA"; - - @Override - public List getSchemas() throws SQLException { - return this.getSchemas(SQL.of(SQL_GET_SCHEMAS)); - } - - private static final String SQL_GET_TABLES = " SELECT table_name AS name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '?'"; - - @Override - public List
getTables(String schema) throws SQLException { - return this.getTables(SQL.of(SQL_GET_TABLES, schema)); - } - - private static final String SQL_GET_VIEWS = "SELECT table_name AS name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = '?' AND table_type = 'VIEW'"; - - @Override - public List getViews(String schema) throws SQLException { - return this.getViews(SQL.of(SQL_GET_VIEWS, schema)); - } - - private static final String SQL_GET_FIELDS = "SELECT column_name AS name, column_type AS type, column_key AS `key`, is_nullable AS `nullable`, column_default AS `default`, extra AS extra, column_comment AS comment FROM information_schema.columns WHERE table_schema = '?' AND table_name = '?'"; - - @Override - public List getFields(String schema, String table) throws SQLException { - return this.getFields(SQL.of(SQL_GET_FIELDS, schema, table)); + return this.metadataCatalog().listCatalogs().stream() + .map(catalog -> { + var database = new Database(); + database.setName(catalog.name()); + return database; + }) + .toList(); } - - } diff --git a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerSQLHintsHandler.java b/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerSQLHintsHandler.java deleted file mode 100644 index dcdc261..0000000 --- a/backend/modules/src/main/java/org.jumpserver.chen.modules/sqlserver/SQLServerSQLHintsHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.jumpserver.chen.modules.sqlserver; - -import org.jumpserver.chen.framework.datasource.ConnectionManager; -import org.jumpserver.chen.framework.datasource.base.BaseSQLHintsHandler; -import org.jumpserver.chen.framework.datasource.entity.resource.Field; -import org.jumpserver.chen.framework.datasource.entity.resource.Schema; -import org.jumpserver.chen.framework.datasource.entity.resource.Table; -import org.jumpserver.chen.framework.datasource.sql.SQL; - -import java.sql.SQLException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class SQLServerSQLHintsHandler extends BaseSQLHintsHandler { - - private final ConnectionManager connectionManager; - - - public SQLServerSQLHintsHandler(ConnectionManager connectionManager) { - this.connectionManager = connectionManager; - } - - - public List
getALlTables(String schema) throws SQLException { - return null; - } - - public List getAllFields(String schema) throws SQLException { - return null; - } - - private static final String GET_ALL_SCHEMAS = "SELECT schema_name FROM INFORMATION_SCHEMA.SCHEMATA"; - - public List getAllSchemas() throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(GET_ALL_SCHEMAS).getSql(), - Schema.class, Map.of("name", 1)); - } - - private static final String GET_ALL_TABLES = "SELECT table_name AS name,table_schema FROM INFORMATION_SCHEMA.TABLES"; - - public List
getAllTables() throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(GET_ALL_TABLES).getSql(), - Table.class, Map.of("name", 1, "schema", 2)); - } - - private static final String GET_ALL_FIELDS = "SELECT column_name AS name, table_schema, table_name FROM INFORMATION_SCHEMA.COLUMNS"; - - public List getAllFields() throws SQLException { - return this.connectionManager.getSqlActuator() - .getObjects(SQL.of(GET_ALL_FIELDS).getSql(), - Field.class, Map.of("name", 1, "schema", 2, "table", 3)); - } - - @Override - public Map> getHints(String nodeKey, String context) throws SQLException { - Map> suggestions = new HashMap<>(); - - var schemas = this.getAllSchemas(); - var schemaNames = schemas.stream().map(Schema::getName).toList(); - - var tables = this.getAllTables(); - var fields = this.getAllFields(); - - schemaNames.forEach(schemaName -> { - var tableNames = tables.stream().filter(table -> table.getSchema().equals(schemaName)).map(Table::getName).toList(); - suggestions.put(schemaName, tableNames); - }); - - var tableNames = tables.stream().map(Table::getName).toList(); - - tableNames.forEach(tableName -> { - var fieldNames = fields.stream().filter(field -> field.getTable().equals(tableName)).map(Field::getName).toList(); - suggestions.put(tableName, fieldNames); - }); - - return suggestions; - } - - -} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/ai/AgentWebSocketHandler.java b/backend/web/src/main/java/org/jumpserver/chen/web/ai/AgentWebSocketHandler.java new file mode 100644 index 0000000..504b3ae --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/ai/AgentWebSocketHandler.java @@ -0,0 +1,534 @@ +package org.jumpserver.chen.web.ai; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.grpc.stub.StreamObserver; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import net.devh.boot.grpc.client.inject.GrpcClient; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.framework.session.impl.JMSSession; +import org.jumpserver.chen.framework.ws.io.Packet; +import org.jumpserver.chen.framework.ws.io.PacketIO; +import org.jumpserver.wisp.ServiceGrpc; +import org.jumpserver.wisp.ServiceOuterClass; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +@Component +@Slf4j +public class AgentWebSocketHandler extends TextWebSocketHandler { + private static final Gson GSON = new Gson(); + private static final int MAX_CLIENT_MESSAGE_BYTES = 320 * 1024; + private static final int MAX_QUESTION_BYTES = 32 * 1024; + private static final int MAX_TOOL_RESULT_BYTES = 256 * 1024; + + private final SqlAgentToolService toolService; + private final Map connections = new ConcurrentHashMap<>(); + private final ThreadPoolExecutor toolExecutor = new ThreadPoolExecutor( + 2, + 4, + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(100), + runnable -> { + Thread thread = new Thread(runnable, "chen-ai-tool"); + thread.setDaemon(true); + return thread; + }, + new ThreadPoolExecutor.AbortPolicy() + ); + + @GrpcClient("wisp") + private ServiceGrpc.ServiceBlockingStub serviceBlockingStub; + + public AgentWebSocketHandler(SqlAgentToolService toolService) { + this.toolService = toolService; + this.toolExecutor.allowCoreThreadTimeOut(true); + } + + @Override + protected void handleTextMessage(WebSocketSession webSocket, TextMessage message) { + String token = (String) webSocket.getAttributes().get("token"); + SessionManager.setContext(token); + if (message.getPayloadLength() > MAX_CLIENT_MESSAGE_BYTES) { + sendError(webSocket, "invalid_request", "AI request is too large", ""); + return; + } + JMSSession session = currentSession(token); + if (session == null) { + sendError(webSocket, "session_closed", "The database session is not active", ""); + closeWebSocket(webSocket); + return; + } + + Packet packet; + try { + packet = GSON.fromJson(message.getPayload(), Packet.class); + } catch (RuntimeException e) { + sendError(webSocket, "invalid_request", "Invalid AI request", ""); + return; + } + if (packet == null) { + sendError(webSocket, "invalid_request", "Invalid AI request", ""); + return; + } + String type = StringUtils.defaultString(packet.getType()); + if ("ping".equals(type)) { + new PacketIO(webSocket).sendPacket("pong", Map.of()); + return; + } + JsonObject data; + try { + data = GSON.toJsonTree(packet.getData()).getAsJsonObject(); + } catch (RuntimeException e) { + sendError(webSocket, "invalid_request", "Invalid AI request", ""); + return; + } + + switch (type) { + case "connect" -> connect(webSocket, token, session, data); + case "ai_request" -> request(webSocket, token, session, data); + case "ai_cancel" -> cancel(webSocket, data); + default -> sendError(webSocket, "invalid_request", "Unsupported AI request", ""); + } + } + + private void connect(WebSocketSession webSocket, String token, JMSSession session, JsonObject data) { + if (connections.containsKey(webSocket.getId())) { + sendError(webSocket, "invalid_request", "AI session is already connected", ""); + return; + } + String language = boundedString(data, "language", 32); + if (StringUtils.isBlank(language)) { + language = session.getLocale().toLanguageTag(); + } + AgentConnection connection = new AgentConnection(webSocket, token, session); + AgentConnection existing = connections.putIfAbsent(webSocket.getId(), connection); + if (existing != null) { + sendError(webSocket, "invalid_request", "AI session is already connected", ""); + return; + } + try { + connection.open(language); + } catch (RuntimeException e) { + connections.remove(webSocket.getId(), connection); + log.warn("Open Chen AI stream failed, sessionId={}", session.getJmsSession().getId(), e); + sendError(webSocket, "ai_unavailable", "AI service is unavailable", ""); + } + } + + private void request(WebSocketSession webSocket, String token, JMSSession session, JsonObject data) { + long startedAt = System.nanoTime(); + AgentConnection connection = connections.get(webSocket.getId()); + if (connection == null) { + sendError(webSocket, "not_connected", "AI session is not connected", ""); + return; + } + String requestId = boundedString(data, "id", 128); + String operation = boundedString(data, "operation", 32).toLowerCase(Locale.ROOT); + String question = boundedString(data, "question", MAX_QUESTION_BYTES); + if (requestId.isBlank() || question.isBlank() + || !SetHolder.OPERATIONS.contains(operation)) { + sendError(webSocket, "invalid_request", "Invalid AI request", requestId); + return; + } + JsonElement context = data.get("context"); + try { + SqlAgentToolService.AgentRequestContext resolved = toolService.resolveRequestContext( + session, context == null ? "" : GSON.toJson(context), operation + ); + connection.request(requestId, operation, question, resolved); + log.info("Chen AI timing request={} stage=submit duration_ms={} outcome=success", + requestId, elapsedMilliseconds(startedAt)); + } catch (IllegalArgumentException e) { + log.info("Chen AI timing request={} stage=submit duration_ms={} outcome=invalid_context", + requestId, elapsedMilliseconds(startedAt)); + sendError(webSocket, "invalid_context", e.getMessage(), requestId); + } catch (RuntimeException e) { + log.info("Chen AI timing request={} stage=submit duration_ms={} outcome=error", + requestId, elapsedMilliseconds(startedAt)); + log.warn("Submit Chen AI request failed, sessionId={}", session.getJmsSession().getId(), e); + sendError(webSocket, "ai_unavailable", "AI service is unavailable", requestId); + } finally { + SessionManager.setContext(token); + } + } + + private void cancel(WebSocketSession webSocket, JsonObject data) { + AgentConnection connection = connections.get(webSocket.getId()); + if (connection == null) { + return; + } + connection.cancel(boundedString(data, "requestId", 128)); + } + + @Override + public void afterConnectionClosed(WebSocketSession webSocket, CloseStatus status) { + AgentConnection connection = connections.remove(webSocket.getId()); + if (connection != null) { + connection.close(); + } + } + + @Override + public void handleTransportError(WebSocketSession webSocket, Throwable exception) { + log.debug("Chen AI websocket transport closed, websocketId={}", webSocket.getId(), exception); + AgentConnection connection = connections.remove(webSocket.getId()); + if (connection != null) { + connection.close(); + } + } + + @PreDestroy + public void shutdown() { + for (AgentConnection connection : connections.values()) { + connection.close(); + } + connections.clear(); + toolExecutor.shutdownNow(); + } + + private JMSSession currentSession(String token) { + var session = SessionManager.getSession(token); + if (!(session instanceof JMSSession jmsSession) + || jmsSession.isClosing() || !jmsSession.isActive()) { + return null; + } + return jmsSession; + } + + private static String boundedString(JsonObject object, String name, int maximum) { + try { + JsonElement value = object.get(name); + String result = value == null || value.isJsonNull() ? "" : value.getAsString(); + return result.length() <= maximum ? result : ""; + } catch (RuntimeException e) { + return ""; + } + } + + private static void sendError(WebSocketSession webSocket, String code, String message, String requestId) { + new PacketIO(webSocket).sendPacket("ai_error", Map.of( + "code", code, + "message", StringUtils.defaultString(message), + "requestId", StringUtils.defaultString(requestId) + )); + } + + private static void closeWebSocket(WebSocketSession webSocket) { + try { + if (webSocket.isOpen()) { + webSocket.close(CloseStatus.POLICY_VIOLATION); + } + } catch (IOException ignored) { + } + } + + private final class AgentConnection { + private final WebSocketSession webSocket; + private final String token; + private final JMSSession databaseSession; + private final AtomicReference activeRequest = new AtomicReference<>(); + private final AtomicBoolean closed = new AtomicBoolean(false); + private volatile StreamObserver requestObserver; + private volatile boolean enabled; + + private AgentConnection(WebSocketSession webSocket, String token, JMSSession databaseSession) { + this.webSocket = webSocket; + this.token = token; + this.databaseSession = databaseSession; + } + + private void open(String language) { + this.requestObserver = ServiceGrpc.newStub(serviceBlockingStub.getChannel()) + .agentSession(new StreamObserver<>() { + @Override + public void onNext(ServiceOuterClass.AgentServerEvent event) { + onServerEvent(event); + } + + @Override + public void onError(Throwable throwable) { + if (!closed.get()) { + log.warn("Chen AI stream failed, sessionId={}", + databaseSession.getJmsSession().getId(), throwable); + sendError(webSocket, "ai_unavailable", "AI service is unavailable", ""); + } + enabled = false; + ActiveRequest active = activeRequest.getAndSet(null); + logRequestTiming(active, "stream_error"); + } + + @Override + public void onCompleted() { + enabled = false; + ActiveRequest active = activeRequest.getAndSet(null); + logRequestTiming(active, "stream_closed"); + } + }); + var identity = this.databaseSession.getJmsSession(); + var open = ServiceOuterClass.AgentSessionOpen.newBuilder() + .setSessionId(identity.getId()) + .setUserId(identity.getUserId()) + .setOrganizationId(identity.getOrgId()) + .setAssetId(identity.getAssetId()) + .setAccountId(identity.getAccountId()) + .setProtocol(identity.getProtocol()) + .setLanguage(language) + .setSurface("sql") + .build(); + send(ServiceOuterClass.AgentClientEvent.newBuilder().setOpen(open).build()); + } + + private void request( + String requestId, + String operation, + String question, + SqlAgentToolService.AgentRequestContext context + ) { + if (!this.enabled) { + sendError(this.webSocket, "ai_unavailable", "AI service is not ready", requestId); + return; + } + ActiveRequest active = new ActiveRequest( + requestId, context, System.nanoTime(), new ConcurrentHashMap<>() + ); + if (!this.activeRequest.compareAndSet(null, active)) { + sendError(this.webSocket, "request_active", "Another AI request is active", requestId); + return; + } + var request = ServiceOuterClass.AgentRequest.newBuilder() + .setId(requestId) + .setOperation(operation) + .setQuestion(question) + .setContextJson(context.sanitizedJson()) + .build(); + try { + send(ServiceOuterClass.AgentClientEvent.newBuilder().setRequest(request).build()); + } catch (RuntimeException e) { + this.activeRequest.compareAndSet(active, null); + throw e; + } + } + + private void cancel(String requestId) { + ActiveRequest active = this.activeRequest.get(); + if (active == null || (StringUtils.isNotBlank(requestId) && !active.id().equals(requestId))) { + return; + } + var cancel = ServiceOuterClass.AgentCancel.newBuilder().setRequestId(active.id()).build(); + send(ServiceOuterClass.AgentClientEvent.newBuilder().setCancel(cancel).build()); + } + + private void onServerEvent(ServiceOuterClass.AgentServerEvent event) { + switch (event.getEventCase()) { + case READY -> onReady(event.getReady()); + case CHAT -> onChat(event.getChat()); + case TOOL_CALL -> onToolCall(event.getToolCall()); + case ERROR -> onAgentError(event.getError()); + case EVENT_NOT_SET -> sendError(this.webSocket, "protocol_error", "Invalid AI response", ""); + } + } + + private void onReady(ServiceOuterClass.AgentReady ready) { + this.enabled = ready.getEnabled(); + new PacketIO(this.webSocket).sendPacket("ai_ready", Map.of( + "enabled", ready.getEnabled(), + "reason", ready.getReason(), + "sessionId", ready.getSessionId(), + "surface", ready.getSurface(), + "provider", ready.getProvider(), + "model", ready.getModel() + )); + } + + private void onChat(ServiceOuterClass.AgentChatMessage chat) { + try { + JsonObject message = JsonParser.parseString(chat.getMessageJson()).getAsJsonObject(); + new PacketIO(this.webSocket).sendPacket("ai_chat", message); + String completedRequestId = idleRequestId(message); + if (StringUtils.isNotBlank(completedRequestId)) { + ActiveRequest active = this.activeRequest.get(); + if (active != null && active.id().equals(completedRequestId)) { + if (this.activeRequest.compareAndSet(active, null)) { + logRequestTiming(active, "complete"); + } + } + } + } catch (RuntimeException e) { + sendError(this.webSocket, "protocol_error", "Invalid AI response", ""); + } + } + + private void onToolCall(ServiceOuterClass.AgentToolCall call) { + ActiveRequest active = this.activeRequest.get(); + if (active == null) { + sendToolResult(call.getId(), "", "No active SQL editor context"); + return; + } + String cacheKey = call.getName() + "\u0000" + call.getArgumentsJson(); + String cachedResult = active.toolResults().get(cacheKey); + if (cachedResult != null) { + log.info("Chen AI timing request={} stage=tool tool={} queue_ms=0 duration_ms=0 outcome=cache_hit", + active.id(), call.getName()); + sendToolResult(call.getId(), cachedResult, ""); + return; + } + long queuedAt = System.nanoTime(); + try { + toolExecutor.execute(() -> { + long startedAt = System.nanoTime(); + String outcome = "success"; + SessionManager.setContext(this.token); + try { + String result = toolService.execute( + this.databaseSession, + active.context(), + call.getName(), + call.getArgumentsJson() + ); + if (result.length() > MAX_TOOL_RESULT_BYTES) { + outcome = "result_too_large"; + sendToolResult(call.getId(), "", "Database metadata result is too large"); + return; + } + active.toolResults().put(cacheKey, result); + sendToolResult(call.getId(), result, ""); + } catch (IllegalArgumentException e) { + outcome = "invalid_request"; + sendToolResult(call.getId(), "", "Invalid database metadata request"); + } catch (SQLException e) { + outcome = "sql_error"; + log.warn("Chen AI metadata query failed, sessionId={}, tool={}", + this.databaseSession.getJmsSession().getId(), call.getName(), e); + sendToolResult(call.getId(), "", "Database metadata request failed"); + } catch (RuntimeException e) { + outcome = "error"; + log.warn("Chen AI tool failed, sessionId={}, tool={}", + this.databaseSession.getJmsSession().getId(), call.getName(), e); + sendToolResult(call.getId(), "", "Database metadata request failed"); + } finally { + log.info("Chen AI timing request={} stage=tool tool={} queue_ms={} duration_ms={} outcome={}", + active.id(), call.getName(), elapsedMilliseconds(queuedAt, startedAt), + elapsedMilliseconds(startedAt), outcome); + } + }); + } catch (RejectedExecutionException e) { + log.info("Chen AI timing request={} stage=tool tool={} queue_ms={} duration_ms=0 outcome=rejected", + active.id(), call.getName(), elapsedMilliseconds(queuedAt)); + sendToolResult(call.getId(), "", "Database metadata queue is full"); + } + } + + private void onAgentError(ServiceOuterClass.AgentError error) { + sendError(this.webSocket, error.getCode(), error.getMessage(), error.getRequestId()); + ActiveRequest active = this.activeRequest.get(); + if (active != null && (error.getRequestId().isBlank() || active.id().equals(error.getRequestId()))) { + if (this.activeRequest.compareAndSet(active, null)) { + logRequestTiming(active, "error"); + } + } + } + + private void sendToolResult(String id, String result, String error) { + var toolResult = ServiceOuterClass.AgentToolResult.newBuilder() + .setId(id) + .setResultJson(result) + .setError(error) + .build(); + send(ServiceOuterClass.AgentClientEvent.newBuilder().setToolResult(toolResult).build()); + } + + private synchronized void send(ServiceOuterClass.AgentClientEvent event) { + if (this.closed.get() || this.requestObserver == null) { + throw new IllegalStateException("AI stream is closed"); + } + this.requestObserver.onNext(event); + } + + private void close() { + if (!this.closed.compareAndSet(false, true)) { + return; + } + this.enabled = false; + ActiveRequest active = this.activeRequest.getAndSet(null); + logRequestTiming(active, "closed"); + StreamObserver observer = this.requestObserver; + if (observer == null) { + return; + } + try { + observer.onNext(ServiceOuterClass.AgentClientEvent.newBuilder() + .setClose(ServiceOuterClass.Empty.getDefaultInstance()).build()); + } catch (RuntimeException ignored) { + } + try { + observer.onCompleted(); + } catch (RuntimeException ignored) { + } + } + + private void logRequestTiming(ActiveRequest active, String outcome) { + if (active == null) { + return; + } + log.info("Chen AI timing request={} stage=request duration_ms={} outcome={}", + active.id(), elapsedMilliseconds(active.startedAt()), outcome); + } + } + + private static String idleRequestId(JsonObject message) { + try { + String requestId = message.getAsJsonObject("metadata").get("requestId").getAsString(); + for (JsonElement value : message.getAsJsonArray("parts")) { + JsonObject part = value.getAsJsonObject(); + if ("data-progress".equals(part.get("type").getAsString()) + && "idle".equals(part.getAsJsonObject("data").get("state").getAsString())) { + return requestId; + } + } + } catch (RuntimeException ignored) { + } + return ""; + } + + private static long elapsedMilliseconds(long startedAt) { + return TimeUnit.NANOSECONDS.toMillis(Math.max(0, System.nanoTime() - startedAt)); + } + + private static long elapsedMilliseconds(long startedAt, long endedAt) { + return TimeUnit.NANOSECONDS.toMillis(Math.max(0, endedAt - startedAt)); + } + + private record ActiveRequest( + String id, + SqlAgentToolService.AgentRequestContext context, + long startedAt, + Map toolResults + ) { + } + + private static final class SetHolder { + private static final java.util.Set OPERATIONS = + java.util.Set.of("generate", "explain", "repair"); + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java b/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java new file mode 100644 index 0000000..5e7bc73 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/ai/SqlAgentToolService.java @@ -0,0 +1,617 @@ +package org.jumpserver.chen.web.ai; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.visitor.SchemaStatVisitor; +import com.alibaba.druid.stat.TableStat; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.console.QueryConsole; +import org.jumpserver.chen.framework.datasource.ConnectionManager; +import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.session.Session; +import org.springframework.stereotype.Service; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +@Service +public class SqlAgentToolService { + private static final Gson GSON = new Gson(); + private static final int MAX_CONTEXT_BYTES = 256 * 1024; + private static final int MAX_SQL_BYTES = 128 * 1024; + private static final int MAX_IDENTIFIER_BYTES = 1024; + private static final int MAX_OBJECTS = 50; + private static final int MAX_ANALYSIS_COLUMNS = 512; + private static final int MAX_INSPECT_TABLES = 8; + private static final int MAX_INSPECT_COLUMNS = 96; + private static final int MAX_INSPECT_RELATIONS = 48; + private static final Set CONTEXT_NODE_TYPES = + Set.of("datasource", "database", "schema", "table", "view"); + private static final Set TOOL_NAMES = Set.of("inspect_schema", "validate_sql"); + private static final Set WORKSPACE_TAB_KINDS = + Set.of("query", "console", "data-view", "database", "none"); + + public AgentRequestContext resolveRequestContext(Session session, String contextJson, String operation) { + if (session == null || session.isClosing() || !session.isActive()) { + throw new IllegalArgumentException("The database session is not active"); + } + if (contextJson == null || contextJson.isBlank() || contextJson.length() > MAX_CONTEXT_BYTES) { + throw new IllegalArgumentException("Invalid SQL editor context"); + } + JsonObject submitted; + try { + submitted = JsonParser.parseString(contextJson).getAsJsonObject(); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL editor context", e); + } + + String nodeKey = boundedString(submitted, "nodeKey", MAX_IDENTIFIER_BYTES, true); + String consoleId = boundedString(submitted, "consoleId", 256, false); + String workspaceTabId = boundedString(submitted, "workspaceTabId", 256, false); + String workspaceTabKind = boundedString(submitted, "workspaceTabKind", 32, false); + if (workspaceTabKind.isBlank()) { + workspaceTabKind = "none"; + } + boolean requiresConsole = !"none".equals(workspaceTabKind) && !"database".equals(workspaceTabKind); + if (!WORKSPACE_TAB_KINDS.contains(workspaceTabKind) + || (requiresConsole && consoleId.isBlank())) { + throw new IllegalArgumentException("Invalid database workspace context"); + } + var console = consoleId.isBlank() ? null : session.getConsoles().get(consoleId); + if (consoleId.isBlank() ? requiresConsole + : console == null || !nodeKey.equals(console.getNodeKey())) { + throw new IllegalArgumentException("Invalid database console context"); + } + + ResourceNodeSnapshot node; + if (console == null) { + node = session.getDatasource().getResourceBrowser().getIndexedNode(nodeKey); + } else { + var context = console.getContext(); + node = new ResourceNodeSnapshot( + context.nodeKey(), context.nodeType(), context.database(), context.schema(), context.table(), null + ); + } + if (node == null || !CONTEXT_NODE_TYPES.contains(node.type())) { + throw new IllegalArgumentException("Invalid database resource context"); + } + + String database = node.database(); + String schema = node.schema(); + String currentContext = ""; + if (console instanceof QueryConsole queryConsole) { + currentContext = queryConsole.getCurrentContext(); + ConnectionManager manager = session.getDatasource().getConnectionManager(); + if (StringUtils.isNotBlank(currentContext)) { + if (StringUtils.equals(manager.getContextKey(), manager.getDatabaseContextKey())) { + database = currentContext; + } else { + schema = currentContext; + } + } + } + + String documentSql = boundedString(submitted, "documentSql", MAX_SQL_BYTES, false); + int selectionFrom = nonNegativeInt(submitted, "selectionFrom"); + int selectionTo = nonNegativeInt(submitted, "selectionTo"); + if (selectionFrom > documentSql.length() || selectionTo > documentSql.length() + || selectionTo < selectionFrom) { + throw new IllegalArgumentException("Invalid SQL editor selection"); + } + boolean hasSelection = selectionTo > selectionFrom; + String selectedSql = hasSelection ? documentSql.substring(selectionFrom, selectionTo) : ""; + long revision = submitted.has("revision") ? submitted.get("revision").getAsLong() : 0; + if (revision < 0) { + throw new IllegalArgumentException("Invalid SQL editor revision"); + } + + Datasource datasource = session.getDatasource(); + JsonObject lastError = "repair".equals(operation) ? sanitizeLastError(submitted.get("lastError")) : null; + if (lastError != null && hasSelection) { + lastError.addProperty("sql", selectedSql); + } + String dialect = StringUtils.defaultString(datasource.getConnectInfo().getDbType()) + .toLowerCase(Locale.ROOT); + String targetSql = hasSelection ? selectedSql : documentSql; + Map sqlAnalysis = StringUtils.isBlank(targetSql) + ? null : validateSQL(datasource.getDruidDbType(), targetSql); + JsonObject sanitized = new JsonObject(); + sanitized.addProperty("dialect", dialect); + addNullableString(sanitized, "database", database); + addNullableString(sanitized, "schema", schema); + sanitized.addProperty("nodeKey", node.key()); + sanitized.addProperty("consoleId", consoleId); + sanitized.addProperty("paneId", boundedString(submitted, "paneId", 256, false)); + sanitized.addProperty("tabId", boundedString(submitted, "tabId", 256, false)); + sanitized.addProperty("workspaceTabId", workspaceTabId); + sanitized.addProperty("workspaceTabKind", workspaceTabKind); + sanitized.addProperty("currentContext", currentContext); + sanitized.addProperty("revision", revision); + sanitized.addProperty("selectionFrom", selectionFrom); + sanitized.addProperty("selectionTo", selectionTo); + sanitized.addProperty("selectedSql", selectedSql); + sanitized.addProperty("documentSql", hasSelection ? "" : documentSql); + if (sqlAnalysis != null) { + sanitized.add("currentSqlAnalysis", GSON.toJsonTree(sqlAnalysis)); + sanitized.add("referencedTables", GSON.toJsonTree(sqlAnalysis.get("tables"))); + } else { + sanitized.add("referencedTables", GSON.toJsonTree(List.of())); + } + if (lastError != null) { + sanitized.add("lastError", lastError); + } + String sanitizedJson = GSON.toJson(sanitized); + if (sanitizedJson.length() > MAX_CONTEXT_BYTES) { + throw new IllegalArgumentException("SQL editor context is too large"); + } + return new AgentRequestContext( + dialect, database, schema, node.table(), node.key(), + sanitizedJson + ); + } + + public String execute(Session session, AgentRequestContext context, String toolName, String argumentsJson) + throws SQLException { + if (session == null || context == null || session.isClosing() || !session.isActive()) { + throw new IllegalStateException("The database session is not active"); + } + toolName = StringUtils.defaultString(toolName).trim().toLowerCase(Locale.ROOT); + if (!TOOL_NAMES.contains(toolName)) { + throw new IllegalArgumentException("Unsupported SQL assistant tool"); + } + if (argumentsJson == null || argumentsJson.length() > 32 * 1024) { + throw new IllegalArgumentException("Invalid SQL assistant tool arguments"); + } + JsonObject arguments; + try { + arguments = JsonParser.parseString(argumentsJson).getAsJsonObject(); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL assistant tool arguments", e); + } + + Datasource datasource = session.getDatasource(); + ConnectionManager connectionManager = datasource.getConnectionManager(); + connectionManager.setDatabaseContext(StringUtils.defaultString(context.database())); + Object result = switch (toolName) { + case "inspect_schema" -> inspectSchema(connectionManager, context, arguments); + case "validate_sql" -> validateSQL( + datasource.getDruidDbType(), boundedString(arguments, "sql", MAX_SQL_BYTES, true) + ); + default -> throw new IllegalArgumentException("Unsupported SQL assistant tool"); + }; + return GSON.toJson(result); + } + + static Map validateSQL(DbType dbType, String sql) { + Map result = new LinkedHashMap<>(); + LinkedHashSet tables = new LinkedHashSet<>(); + LinkedHashSet columns = new LinkedHashSet<>(); + List errors = new ArrayList<>(); + List statements; + try { + statements = SQLUtils.parseStatements(sql, dbType); + if (statements.isEmpty()) { + throw new IllegalArgumentException("SQL statement is empty"); + } + } catch (RuntimeException e) { + result.put("valid", false); + result.put("statementCount", 0); + result.put("statementType", "UNKNOWN"); + result.put("riskLevel", 0); + result.put("riskReason", "SQL syntax validation failed"); + result.put("tables", List.of()); + result.put("columns", List.of()); + result.put("errors", List.of(safeError(e))); + return result; + } + + int riskLevel = 0; + String statementType = statements.size() == 1 ? statementType(statements.get(0)) : "MULTI"; + for (SQLStatement statement : statements) { + riskLevel = Math.max(riskLevel, riskLevel(statementType(statement))); + try { + SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(dbType); + statement.accept(visitor); + for (TableStat.Name table : visitor.getTables().keySet()) { + addBounded(tables, table.toString(), MAX_OBJECTS); + } + Collection statementColumns = visitor.getColumns(); + for (TableStat.Column column : statementColumns) { + addBounded(columns, column.toString(), MAX_ANALYSIS_COLUMNS); + } + } catch (RuntimeException ignored) { + // Some vendor-specific statements are syntactically valid but do not support schema statistics. + } + } + result.put("valid", true); + result.put("statementCount", statements.size()); + result.put("statementType", statementType); + result.put("riskLevel", riskLevel); + result.put("riskReason", riskReason(riskLevel, statements.size())); + result.put("tables", List.copyOf(tables)); + result.put("columns", List.copyOf(columns)); + result.put("errors", errors); + return result; + } + + private Map inspectSchema( + ConnectionManager manager, AgentRequestContext context, JsonObject arguments + ) throws SQLException { + String query = boundedString(arguments, "query", MAX_IDENTIFIER_BYTES, false).trim(); + List requestedTables = boundedStringList(arguments, "tables", MAX_INSPECT_TABLES); + if (query.isBlank() && requestedTables.isEmpty()) { + throw new IllegalArgumentException("Schema inspection query or tables are required"); + } + String requestedSchema = boundedString(arguments, "schema", MAX_IDENTIFIER_BYTES, false); + try (Connection connection = manager.getConnection()) { + DatabaseMetaData metadata = connection.getMetaData(); + MetadataScope scope = metadataScope(connection, context, requestedSchema); + Map> candidates = new LinkedHashMap<>(); + List missingTables = new ArrayList<>(); + for (String requestedTable : requestedTables) { + String table = unqualifiedTableName(requestedTable); + Map item = findTable(metadata, scope, table); + if (item == null) { + missingTables.add(requestedTable); + } else { + candidates.putIfAbsent(metadataKey(item), item); + } + } + if (!query.isBlank() && candidates.size() < MAX_INSPECT_TABLES) { + String pattern = "%" + escapeMetadataPattern(query, metadata.getSearchStringEscape()) + "%"; + try (ResultSet rows = metadata.getTables( + scope.catalog(), scope.schema(), pattern, + new String[]{"TABLE", "VIEW", "MATERIALIZED VIEW"} + )) { + while (rows.next() && candidates.size() < MAX_INSPECT_TABLES) { + Map item = tableInfo(rows); + candidates.putIfAbsent(metadataKey(item), item); + } + } + } + List> objects = new ArrayList<>(candidates.size()); + for (Map tableInfo : candidates.values()) { + objects.add(describeTable(metadata, scope, tableInfo)); + } + Map result = new LinkedHashMap<>(); + result.put("objects", objects); + result.put("missingTables", missingTables); + result.put("truncated", candidates.size() == MAX_INSPECT_TABLES); + return result; + } + } + + private Map describeTable( + DatabaseMetaData metadata, MetadataScope scope, Map tableInfo + ) throws SQLException { + String table = String.valueOf(tableInfo.get("name")); + List> columns = readColumns(metadata, scope, table, MAX_INSPECT_COLUMNS); + Set primaryKeys = readPrimaryKeys(metadata, scope, table, MAX_INSPECT_COLUMNS); + for (Map column : columns) { + column.put("primaryKey", primaryKeys.contains(column.get("name"))); + } + Map result = new LinkedHashMap<>(tableInfo); + result.put("columns", columns); + result.put("foreignKeys", readForeignKeys(metadata, scope, table, MAX_INSPECT_RELATIONS)); + result.put("indexes", readIndexes(metadata, scope, table, MAX_INSPECT_RELATIONS)); + result.put("truncated", columns.size() == MAX_INSPECT_COLUMNS); + return result; + } + + private Map findTable(DatabaseMetaData metadata, MetadataScope scope, String table) + throws SQLException { + String pattern = escapeMetadataPattern(table, metadata.getSearchStringEscape()); + try (ResultSet rows = metadata.getTables( + scope.catalog(), scope.schema(), pattern, + new String[]{"TABLE", "VIEW", "MATERIALIZED VIEW"} + )) { + while (rows.next()) { + if (table.equalsIgnoreCase(rows.getString("TABLE_NAME"))) { + return tableInfo(rows); + } + } + return null; + } + } + + private static Map tableInfo(ResultSet rows) throws SQLException { + Map result = new LinkedHashMap<>(); + result.put("database", rows.getString("TABLE_CAT")); + result.put("schema", rows.getString("TABLE_SCHEM")); + result.put("name", rows.getString("TABLE_NAME")); + result.put("type", rows.getString("TABLE_TYPE")); + result.put("comment", rows.getString("REMARKS")); + return result; + } + + private static String metadataKey(Map item) { + return (StringUtils.defaultString((String) item.get("database")) + "\u0000" + + StringUtils.defaultString((String) item.get("schema")) + "\u0000" + + StringUtils.defaultString((String) item.get("name"))).toLowerCase(Locale.ROOT); + } + + private List> readColumns( + DatabaseMetaData metadata, MetadataScope scope, String table, int maximum + ) + throws SQLException { + List> columns = new ArrayList<>(); + try (ResultSet rows = metadata.getColumns(scope.catalog(), scope.schema(), table, "%")) { + while (rows.next() && columns.size() < maximum) { + Map column = new LinkedHashMap<>(); + column.put("name", rows.getString("COLUMN_NAME")); + column.put("type", rows.getString("TYPE_NAME")); + column.put("jdbcType", rows.getInt("DATA_TYPE")); + column.put("size", rows.getInt("COLUMN_SIZE")); + column.put("scale", rows.getInt("DECIMAL_DIGITS")); + column.put("nullable", rows.getInt("NULLABLE") != DatabaseMetaData.columnNoNulls); + column.put("defaultValue", rows.getString("COLUMN_DEF")); + column.put("comment", rows.getString("REMARKS")); + column.put("position", rows.getInt("ORDINAL_POSITION")); + columns.add(column); + } + } + return columns; + } + + private Set readPrimaryKeys( + DatabaseMetaData metadata, MetadataScope scope, String table, int maximum + ) + throws SQLException { + Set keys = new LinkedHashSet<>(); + try (ResultSet rows = metadata.getPrimaryKeys(scope.catalog(), scope.schema(), table)) { + while (rows.next() && keys.size() < maximum) { + keys.add(rows.getString("COLUMN_NAME")); + } + } + return keys; + } + + private List> readForeignKeys( + DatabaseMetaData metadata, MetadataScope scope, String table, int maximum + ) + throws SQLException { + List> keys = new ArrayList<>(); + try (ResultSet rows = metadata.getImportedKeys(scope.catalog(), scope.schema(), table)) { + while (rows.next() && keys.size() < maximum) { + Map key = new LinkedHashMap<>(); + key.put("name", rows.getString("FK_NAME")); + key.put("column", rows.getString("FKCOLUMN_NAME")); + key.put("referencedSchema", rows.getString("PKTABLE_SCHEM")); + key.put("referencedTable", rows.getString("PKTABLE_NAME")); + key.put("referencedColumn", rows.getString("PKCOLUMN_NAME")); + keys.add(key); + } + } + return keys; + } + + private List> readIndexes( + DatabaseMetaData metadata, MetadataScope scope, String table, int maximum + ) + throws SQLException { + List> indexes = new ArrayList<>(); + try (ResultSet rows = metadata.getIndexInfo(scope.catalog(), scope.schema(), table, false, false)) { + while (rows.next() && indexes.size() < maximum) { + String column = rows.getString("COLUMN_NAME"); + if (column == null) { + continue; + } + Map index = new LinkedHashMap<>(); + index.put("name", rows.getString("INDEX_NAME")); + index.put("column", column); + index.put("unique", !rows.getBoolean("NON_UNIQUE")); + index.put("position", rows.getInt("ORDINAL_POSITION")); + indexes.add(index); + } + } + return indexes; + } + + private MetadataScope metadataScope( + Connection connection, AgentRequestContext context, String requestedSchema + ) throws SQLException { + String schema = StringUtils.defaultIfBlank(requestedSchema, context.schema()); + String database = StringUtils.defaultIfBlank(context.database(), connection.getCatalog()); + if ("mysql".equals(context.dialect()) || "mariadb".equals(context.dialect())) { + return new MetadataScope(StringUtils.defaultIfBlank(database, schema), null); + } + if ("oracle".equals(context.dialect()) || "dameng".equals(context.dialect())) { + return new MetadataScope(null, StringUtils.defaultIfBlank(schema, safeSchema(connection))); + } + return new MetadataScope(database, StringUtils.defaultIfBlank(schema, safeSchema(connection))); + } + + private static String safeSchema(Connection connection) { + try { + return connection.getSchema(); + } catch (SQLException | AbstractMethodError ignored) { + return null; + } + } + + private static String escapeMetadataPattern(String input, String escape) { + String escaped = input; + if (StringUtils.isNotEmpty(escape)) { + escaped = escaped.replace(escape, escape + escape) + .replace("%", escape + "%") + .replace("_", escape + "_"); + } + return escaped; + } + + private static String statementType(SQLStatement statement) { + String name = statement.getClass().getSimpleName().toUpperCase(Locale.ROOT); + if (name.startsWith("SQL")) { + name = name.substring(3); + } + if (name.endsWith("STATEMENT")) { + name = name.substring(0, name.length() - "STATEMENT".length()); + } + return name; + } + + private static int riskLevel(String statementType) { + String type = statementType.toUpperCase(Locale.ROOT); + if (type.contains("SELECT") || type.contains("SHOW") || type.contains("DESC") + || type.contains("EXPLAIN") || type.contains("WITH")) { + return 1; + } + if (type.contains("INSERT") || type.contains("UPDATE") || type.contains("DELETE") + || type.contains("MERGE") || type.contains("REPLACE")) { + return 3; + } + if (type.contains("DROP") || type.contains("TRUNCATE") || type.contains("GRANT") + || type.contains("REVOKE")) { + return 4; + } + if (type.contains("CREATE") || type.contains("ALTER") || type.contains("RENAME")) { + return 3; + } + return 2; + } + + private static String riskReason(int riskLevel, int statementCount) { + String base = switch (riskLevel) { + case 1 -> "Read-only SQL statement"; + case 3 -> "SQL may change database data or schema"; + case 4 -> "SQL may remove data or change privileges"; + default -> "SQL statement requires manual review"; + }; + return statementCount > 1 ? base + "; contains multiple statements" : base; + } + + private static String safeError(RuntimeException error) { + String message = StringUtils.defaultIfBlank(error.getMessage(), error.getClass().getSimpleName()); + return message.length() > 4096 ? message.substring(0, 4096) : message; + } + + private static void addBounded(Set values, String value, int max) { + if (values.size() < max && StringUtils.isNotBlank(value)) { + values.add(value); + } + } + + static JsonObject sanitizeLastError(JsonElement value) { + if (value == null || value.isJsonNull()) { + return null; + } + JsonObject submitted; + try { + submitted = value.getAsJsonObject(); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL error context", e); + } + JsonObject sanitized = new JsonObject(); + sanitized.addProperty("kind", boundedString(submitted, "kind", 32, false)); + sanitized.addProperty("title", boundedString(submitted, "title", 256, false)); + sanitized.addProperty("message", boundedString(submitted, "message", 8 * 1024, true)); + sanitized.addProperty("sql", boundedString(submitted, "sql", MAX_SQL_BYTES, false)); + sanitized.addProperty("sqlState", boundedString(submitted, "sqlState", 128, false)); + try { + if (submitted.has("vendorCode") && !submitted.get("vendorCode").isJsonNull()) { + sanitized.addProperty("vendorCode", submitted.get("vendorCode").getAsInt()); + } + if (submitted.has("timestamp") && !submitted.get("timestamp").isJsonNull()) { + sanitized.addProperty("timestamp", submitted.get("timestamp").getAsLong()); + } + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid SQL error context", e); + } + return sanitized; + } + + private static int nonNegativeInt(JsonObject object, String name) { + if (!object.has(name)) { + return 0; + } + int value = object.get(name).getAsInt(); + if (value < 0) { + throw new IllegalArgumentException("Invalid SQL editor selection"); + } + return value; + } + + private static List boundedStringList(JsonObject object, String name, int maximum) { + JsonElement value = object.get(name); + if (value == null || value.isJsonNull()) { + return List.of(); + } + LinkedHashMap values = new LinkedHashMap<>(); + try { + for (JsonElement element : value.getAsJsonArray()) { + String text = element.getAsString().trim(); + if (text.isEmpty() || text.length() > MAX_IDENTIFIER_BYTES) { + throw new IllegalArgumentException("Invalid " + name); + } + values.putIfAbsent(text.toLowerCase(Locale.ROOT), text); + if (values.size() > maximum) { + throw new IllegalArgumentException("Too many " + name); + } + } + } catch (IllegalArgumentException e) { + throw e; + } catch (RuntimeException e) { + throw new IllegalArgumentException("Invalid " + name, e); + } + return List.copyOf(values.values()); + } + + private static String unqualifiedTableName(String value) { + String result = StringUtils.trimToEmpty(value); + int separator = result.lastIndexOf('.'); + if (separator >= 0) { + result = result.substring(separator + 1).trim(); + } + if (result.length() >= 2 && ((result.startsWith("\"") && result.endsWith("\"")) + || (result.startsWith("`") && result.endsWith("`")) + || (result.startsWith("[") && result.endsWith("]")))) { + result = result.substring(1, result.length() - 1); + } + if (result.isBlank()) { + throw new IllegalArgumentException("Invalid table name"); + } + return result; + } + + private static String boundedString(JsonObject object, String name, int maximum, boolean required) { + JsonElement value = object.get(name); + String text = value == null || value.isJsonNull() ? "" : value.getAsString(); + if ((required && text.isBlank()) || text.length() > maximum) { + throw new IllegalArgumentException("Invalid " + name); + } + return text; + } + + private static void addNullableString(JsonObject object, String name, String value) { + object.addProperty(name, StringUtils.defaultString(value)); + } + + public record AgentRequestContext( + String dialect, + String database, + String schema, + String table, + String nodeKey, + String sanitizedJson + ) { + } + + private record MetadataScope(String catalog, String schema) { + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java b/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java index 3e79d5c..56f62c2 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/config/WebSocketConfig.java @@ -1,6 +1,7 @@ package org.jumpserver.chen.web.config; import lombok.extern.slf4j.Slf4j; +import org.jumpserver.chen.web.ai.AgentWebSocketHandler; import org.jumpserver.chen.framework.session.SessionManager; import org.jumpserver.chen.framework.ws.ConsoleWebSocketHandler; import org.jumpserver.chen.framework.ws.DBConsoleWebsocketHandler; @@ -39,11 +40,12 @@ public ServletServerContainerFactoryBean createWebSocketContainer() { @Bean - public WebSocketHandlerMapping chenWebSocketHandlerMapping() { + public WebSocketHandlerMapping chenWebSocketHandlerMapping(AgentWebSocketHandler agentWebSocketHandler) { var handlers = new LinkedHashMap(); handlers.put("/ws/session", createRequestHandler(new SessionWebSocketHandler())); handlers.put("/ws/console", createRequestHandler(new ConsoleWebSocketHandler())); handlers.put("/ws/db-console", createRequestHandler(new DBConsoleWebsocketHandler())); + handlers.put("/ws/ai", createRequestHandler(agentWebSocketHandler)); var mapping = new WebSocketHandlerMapping(); // 与 Spring 默认 WebSocket 映射一致,确保 WS 请求优先于普通 MVC 映射处理。 diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java b/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java index 1a5bc4f..247bb3e 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/controller/ResourceController.java @@ -5,9 +5,17 @@ import org.jumpserver.chen.framework.datasource.entity.action.EventEmitter; import org.jumpserver.chen.framework.datasource.entity.form.FormData; import org.jumpserver.chen.framework.datasource.entity.resource.TreeNode; -import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.entity.RelationMetadataPage; import org.jumpserver.chen.web.entity.GetHintsRequest; +import org.jumpserver.chen.web.entity.MetadataColumnsRequest; +import org.jumpserver.chen.web.entity.MetadataColumnsResponse; +import org.jumpserver.chen.web.entity.MetadataRelationsRequest; +import org.jumpserver.chen.web.entity.SchemaOverviewMetadata; +import org.jumpserver.chen.web.entity.SchemaOverviewRequest; +import org.jumpserver.chen.web.service.HintsService; import org.jumpserver.chen.web.service.ResourceService; +import org.jumpserver.chen.web.service.SchemaOverviewService; +import org.jumpserver.chen.web.service.SqlMetadataService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -23,6 +31,15 @@ public class ResourceController { @Autowired private ResourceService resourceService; + @Autowired + private SqlMetadataService sqlMetadataService; + + @Autowired + private SchemaOverviewService schemaOverviewService; + + @Autowired + private HintsService hintsService; + @PostMapping("/children") public List getChild(@RequestBody(required = false) TreeNode node, @@ -52,13 +69,25 @@ public EventEmitter submitResourceFormData(@RequestBody FormData form) throws SQ } @PostMapping("/hints") - public Map> getHints(@RequestBody GetHintsRequest request) throws SQLException { - return SessionManager - .getCurrentSession() - .getDatasource() - .getResourceBrowser() - .getSQLHintsHandler() - .getHints(request.getNodeKey(), request.getContext()); + public Map> getHints(@RequestBody GetHintsRequest request) { + return this.hintsService.getHints(request.getNodeKey(), request.getContext()); + } + + @PostMapping("/metadata/relations") + public RelationMetadataPage getMetadataRelations( + @RequestBody MetadataRelationsRequest request + ) { + return this.sqlMetadataService.listRelations(request); + } + + @PostMapping("/metadata/columns") + public MetadataColumnsResponse getMetadataColumns(@RequestBody MetadataColumnsRequest request) { + return this.sqlMetadataService.listColumns(request); + } + + @PostMapping("/metadata/schema-overview") + public SchemaOverviewMetadata getSchemaOverview(@RequestBody SchemaOverviewRequest request) { + return this.schemaOverviewService.getSchemaOverview(request); } } diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsRequest.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsRequest.java new file mode 100644 index 0000000..87cbedd --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsRequest.java @@ -0,0 +1,12 @@ +package org.jumpserver.chen.web.entity; + +import lombok.Data; + +import java.util.List; + +@Data +public class MetadataColumnsRequest { + private String nodeKey; + private String context; + private List relations; +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsResponse.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsResponse.java new file mode 100644 index 0000000..df9dd98 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataColumnsResponse.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; + +public record MetadataColumnsResponse(List items) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataRelationsRequest.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataRelationsRequest.java new file mode 100644 index 0000000..f56e9c3 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/MetadataRelationsRequest.java @@ -0,0 +1,11 @@ +package org.jumpserver.chen.web.entity; + +import lombok.Data; + +@Data +public class MetadataRelationsRequest { + private String nodeKey; + private String context; + private String prefix; + private Integer limit; +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/QualifiedRelation.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/QualifiedRelation.java new file mode 100644 index 0000000..bd940ab --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/QualifiedRelation.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.web.entity; + +public record QualifiedRelation(String catalog, String schema, String name, String kind) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationColumnsMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationColumnsMetadata.java new file mode 100644 index 0000000..2b2ffd4 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationColumnsMetadata.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; + +public record RelationColumnsMetadata(QualifiedRelation relation, List columns) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationMetadataPage.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationMetadataPage.java new file mode 100644 index 0000000..092ed8a --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/RelationMetadataPage.java @@ -0,0 +1,6 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; + +public record RelationMetadataPage(List items, boolean truncated) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewMetadata.java new file mode 100644 index 0000000..1f230c0 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewMetadata.java @@ -0,0 +1,57 @@ +package org.jumpserver.chen.web.entity; + +import java.util.List; + +public record SchemaOverviewMetadata( + String catalog, + String schema, + Capabilities capabilities, + List tables, + List views, + List indexes, + String ddl +) { + public record Capabilities( + boolean tableRows, + boolean tableSize, + boolean tableEngine, + boolean tableCharacterSet, + boolean tableCollation, + boolean tableComment, + boolean viewComment, + boolean indexes, + boolean ddl + ) { + } + + public record TableMetadata( + String name, + String schema, + Long estimatedRows, + Long totalSizeBytes, + String engine, + String characterSet, + String collation, + String comment + ) { + } + + public record ViewMetadata( + String name, + String schema, + String type, + String comment + ) { + } + + public record IndexMetadata( + String name, + String schema, + String table, + List columns, + Boolean unique, + String method, + String definition + ) { + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewRequest.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewRequest.java new file mode 100644 index 0000000..e1ac124 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SchemaOverviewRequest.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.web.entity; + +public record SchemaOverviewRequest(String nodeKey) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/entity/SqlColumnMetadata.java b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SqlColumnMetadata.java new file mode 100644 index 0000000..c0d946d --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/entity/SqlColumnMetadata.java @@ -0,0 +1,4 @@ +package org.jumpserver.chen.web.entity; + +public record SqlColumnMetadata(String name, String dataType, boolean nullable) { +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java b/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java index 92defe4..5383921 100644 --- a/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java +++ b/backend/web/src/main/java/org/jumpserver/chen/web/interceptor/SessionInterceptor.java @@ -16,8 +16,8 @@ public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Objec } //1. 从header 中获取 token String token = req.getHeader("token"); - //2. 判断 token 是否认证 - if (token == null || token.isEmpty() || SessionManager.getSession(token) == null || !SessionManager.getSession(token).isActive()) { + //2. /api/profile 等请求发生在 websocket 建立前,此时 session 已创建但尚未 active。 + if (token == null || token.isEmpty() || SessionManager.getSession(token) == null) { //2.1 认证失败,返回错误信息 resp.setStatus(401); resp.getWriter().write("Unauthorized"); diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/HintsService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/HintsService.java new file mode 100644 index 0000000..2ff9339 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/HintsService.java @@ -0,0 +1,64 @@ +package org.jumpserver.chen.web.service; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.exception.ChenException; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Service +public class HintsService { + private static final Set QUERY_NODE_TYPES = Set.of("datasource", "database", "schema", "table"); + private static final Set HINT_KINDS = Set.of(RelationKind.TABLE, RelationKind.VIEW); + + public Map> getHints(String nodeKey, String context) { + var session = SessionManager.getCurrentSession(); + if (!session.enableAutoComplete()) { + return Map.of(); + } + + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveNode(browser, nodeKey); + try { + var scope = browser.resolveScope(node, context); + var catalog = datasource.getMetadataCatalog(); + var relations = catalog.listRelations(scope, HINT_KINDS); + var columnsByName = new LinkedHashMap>(); + for (var column : catalog.listColumns(relations.stream().map(RelationMetadata::ref).toList())) { + columnsByName.computeIfAbsent(column.owner().name(), ignored -> new ArrayList<>()).add(column.name()); + } + + var suggestions = new LinkedHashMap>(); + suggestions.put(StringUtils.defaultString(scope.schema()), + relations.stream().map(relation -> relation.ref().name()).toList()); + for (var relation : relations) { + suggestions.put(relation.ref().name(), columnsByName.getOrDefault(relation.ref().name(), List.of())); + } + return suggestions; + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException("Failed to load SQL hints", e); + } + } + + private ResourceNodeSnapshot resolveNode(ResourceBrowser browser, String nodeKey) { + if (StringUtils.isBlank(nodeKey)) { + throw new ChenException("Invalid metadata context"); + } + var node = browser.getIndexedNode(nodeKey); + if (node == null || !QUERY_NODE_TYPES.contains(node.type())) { + throw new ChenException("Invalid metadata context"); + } + return node; + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/SchemaOverviewService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/SchemaOverviewService.java new file mode 100644 index 0000000..c0a1552 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/SchemaOverviewService.java @@ -0,0 +1,128 @@ +package org.jumpserver.chen.web.service; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.IndexPart; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCapabilities; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.ObjectStatistics; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.entity.SchemaOverviewMetadata; +import org.jumpserver.chen.web.entity.SchemaOverviewRequest; +import org.jumpserver.chen.web.exception.ChenException; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +@Service +public class SchemaOverviewService { + private static final Set TABLE_KINDS = Set.of(RelationKind.TABLE); + private static final Set VIEW_KINDS = Set.of(RelationKind.VIEW, RelationKind.MATERIALIZED_VIEW); + + public SchemaOverviewMetadata getSchemaOverview(SchemaOverviewRequest request) { + var session = SessionManager.getCurrentSession(); + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveSchemaNode(browser, request == null ? null : request.nodeKey()); + try { + var scope = new RelationScope(node.database(), node.schema()); + var catalog = datasource.getMetadataCatalog(); + return new SchemaOverviewMetadata( + scope.catalog(), + scope.schema(), + this.toCapabilities(catalog.capabilities()), + this.loadTables(catalog, scope), + this.loadViews(catalog, scope), + this.loadIndexes(catalog, scope), + catalog.getSchemaDefinition(scope) + ); + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException("Failed to load schema overview metadata", e); + } + } + + private List loadTables(MetadataCatalog catalog, RelationScope scope) + throws SQLException { + var relations = catalog.listRelations(scope, TABLE_KINDS); + var statsByName = new HashMap(); + for (var stat : catalog.listStatistics(scope)) { + statsByName.put(stat.ref().name(), stat); + } + return relations.stream().map(relation -> { + var stat = statsByName.get(relation.ref().name()); + return new SchemaOverviewMetadata.TableMetadata( + relation.ref().name(), + relation.ref().schema(), + stat == null ? null : stat.estimatedRows(), + stat == null ? null : stat.totalSizeBytes(), + relation.engine(), + relation.characterSet(), + relation.collation(), + relation.comment() + ); + }).toList(); + } + + private List loadViews(MetadataCatalog catalog, RelationScope scope) + throws SQLException { + return catalog.listRelations(scope, VIEW_KINDS).stream() + .map(relation -> new SchemaOverviewMetadata.ViewMetadata( + relation.ref().name(), + relation.ref().schema(), + this.viewType(relation.ref().kind()), + relation.comment() + )) + .toList(); + } + + private List loadIndexes(MetadataCatalog catalog, RelationScope scope) + throws SQLException { + return catalog.listIndexes(scope).stream() + .map(index -> new SchemaOverviewMetadata.IndexMetadata( + index.name(), + index.owner().schema(), + index.owner().name(), + index.parts().stream().map(IndexPart::columnName).filter(Objects::nonNull).toList(), + index.unique(), + index.method(), + index.definition() + )) + .toList(); + } + + private SchemaOverviewMetadata.Capabilities toCapabilities(MetadataCapabilities capabilities) { + return new SchemaOverviewMetadata.Capabilities( + capabilities.tableRows(), + capabilities.tableSize(), + capabilities.tableEngine(), + capabilities.tableCharacterSet(), + capabilities.tableCollation(), + capabilities.tableComment(), + capabilities.viewComment(), + capabilities.indexes(), + capabilities.definitions() + ); + } + + private String viewType(RelationKind kind) { + return kind == RelationKind.MATERIALIZED_VIEW ? "MATERIALIZED VIEW" : "VIEW"; + } + + private ResourceNodeSnapshot resolveSchemaNode(ResourceBrowser browser, String nodeKey) { + if (StringUtils.isBlank(nodeKey)) { + throw new ChenException("Invalid schema overview context"); + } + var node = browser.getIndexedNode(nodeKey); + if (node == null || !"schema".equals(node.type()) || StringUtils.isBlank(node.schema())) { + throw new ChenException("Invalid schema overview context"); + } + return node; + } +} diff --git a/backend/web/src/main/java/org/jumpserver/chen/web/service/SqlMetadataService.java b/backend/web/src/main/java/org/jumpserver/chen/web/service/SqlMetadataService.java new file mode 100644 index 0000000..5f666d9 --- /dev/null +++ b/backend/web/src/main/java/org/jumpserver/chen/web/service/SqlMetadataService.java @@ -0,0 +1,237 @@ +package org.jumpserver.chen.web.service; + +import org.apache.commons.lang3.StringUtils; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.resource.ResourceNodeSnapshot; +import org.jumpserver.chen.framework.datasource.metadata.MetadataCatalog; +import org.jumpserver.chen.framework.datasource.metadata.ObjectRef; +import org.jumpserver.chen.framework.datasource.metadata.RelationKind; +import org.jumpserver.chen.framework.datasource.metadata.RelationMetadata; +import org.jumpserver.chen.framework.datasource.metadata.RelationScope; +import org.jumpserver.chen.framework.datasource.metadata.SchemaMetadata; +import org.jumpserver.chen.framework.session.SessionManager; +import org.jumpserver.chen.web.entity.MetadataColumnsRequest; +import org.jumpserver.chen.web.entity.MetadataColumnsResponse; +import org.jumpserver.chen.web.entity.MetadataRelationsRequest; +import org.jumpserver.chen.web.entity.QualifiedRelation; +import org.jumpserver.chen.web.entity.RelationColumnsMetadata; +import org.jumpserver.chen.web.entity.RelationMetadataPage; +import org.jumpserver.chen.web.entity.SqlColumnMetadata; +import org.jumpserver.chen.web.exception.ChenException; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +@Service +public class SqlMetadataService { + private static final Set QUERY_NODE_TYPES = Set.of("datasource", "database", "schema", "table"); + private static final Set RELATION_KIND_STRINGS = Set.of("table", "view"); + private static final Set COMPLETION_KINDS = Set.of(RelationKind.TABLE, RelationKind.VIEW); + private static final int DEFAULT_RELATION_LIMIT = 100; + private static final int MAX_RELATION_LIMIT = 200; + private static final int MAX_COLUMN_RELATIONS = 20; + + public RelationMetadataPage listRelations(MetadataRelationsRequest request) { + var session = SessionManager.getCurrentSession(); + if (!session.enableAutoComplete()) { + return new RelationMetadataPage(List.of(), false); + } + + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveNode(browser, request == null ? null : request.getNodeKey()); + try { + var scope = browser.resolveScope(node, request == null ? null : request.getContext()); + var catalog = datasource.getMetadataCatalog(); + var relations = catalog.listRelations(scope, COMPLETION_KINDS); + var normalizedPrefix = StringUtils.defaultString(request.getPrefix()).trim().toLowerCase(Locale.ROOT); + var filtered = relations.stream() + .filter(relation -> normalizedPrefix.isEmpty() + || relation.ref().name().toLowerCase(Locale.ROOT).startsWith(normalizedPrefix) + || (relation.ref().schema() + "." + relation.ref().name()) + .toLowerCase(Locale.ROOT).startsWith(normalizedPrefix)) + .sorted(Comparator.comparing((RelationMetadata relation) -> relation.ref().schema(), String.CASE_INSENSITIVE_ORDER) + .thenComparing(relation -> relation.ref().name(), String.CASE_INSENSITIVE_ORDER) + .thenComparing(relation -> relation.ref().kind().code())) + .toList(); + var boundedLimit = request.getLimit() == null + ? DEFAULT_RELATION_LIMIT + : Math.max(1, Math.min(request.getLimit(), MAX_RELATION_LIMIT)); + var truncated = filtered.size() > boundedLimit; + var items = filtered.stream().limit(boundedLimit) + .map(relation -> new QualifiedRelation( + relation.ref().catalog(), relation.ref().schema(), relation.ref().name(), relation.ref().kind().code())) + .toList(); + return new RelationMetadataPage(items, truncated); + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException("Failed to load SQL relation metadata", e); + } + } + + public MetadataColumnsResponse listColumns(MetadataColumnsRequest request) { + var session = SessionManager.getCurrentSession(); + if (!session.enableAutoComplete()) { + return new MetadataColumnsResponse(List.of()); + } + + var datasource = session.getDatasource(); + var browser = datasource.getResourceBrowser(); + var node = this.resolveNode(browser, request == null ? null : request.getNodeKey()); + try { + var scope = browser.resolveScope(node, request == null ? null : request.getContext()); + var catalog = datasource.getMetadataCatalog(); + var refs = this.resolveRelations(catalog, scope, request.getRelations()); + var columns = catalog.listColumns(refs); + var columnsByRef = new LinkedHashMap>(); + for (var column : columns) { + columnsByRef.computeIfAbsent(column.owner(), ignored -> new ArrayList<>()) + .add(new SqlColumnMetadata(column.name(), column.nativeType(), column.nullable())); + } + var items = refs.stream() + .map(ref -> new RelationColumnsMetadata( + new QualifiedRelation(ref.catalog(), ref.schema(), ref.name(), ref.kind().code()), + columnsByRef.getOrDefault(ref, List.of()))) + .toList(); + return new MetadataColumnsResponse(items); + } catch (SQLException | IllegalArgumentException e) { + throw new ChenException("Failed to load SQL column metadata", e); + } + } + + private List resolveRelations( + MetadataCatalog catalog, RelationScope scope, List requestedRelations + ) throws SQLException { + if (requestedRelations == null || requestedRelations.isEmpty()) { + return List.of(); + } + if (requestedRelations.size() > MAX_COLUMN_RELATIONS) { + throw new IllegalArgumentException("Too many relations in one metadata request"); + } + + var availableSchemas = catalog.listSchemas(scope.catalog()).stream().map(SchemaMetadata::name).toList(); + var relationsBySchema = new LinkedHashMap>(); + var canonicalRequests = new LinkedHashMap(); + for (var requested : requestedRelations) { + this.validateRequestedRelation(requested, scope.catalog()); + var requestedSchema = StringUtils.defaultIfBlank(requested.schema(), scope.schema()); + var canonicalSchema = this.resolveCanonicalIdentifier(availableSchemas, requestedSchema, "Unknown relation schema"); + + var availableRelations = relationsBySchema.get(canonicalSchema); + if (availableRelations == null) { + availableRelations = this.loadRelationsByKey(catalog, scope.catalog(), canonicalSchema); + relationsBySchema.put(canonicalSchema, availableRelations); + } + + var kind = RelationKind.fromCode(requested.kind()); + var key = new RelationKey(canonicalSchema, requested.name(), kind); + var canonical = availableRelations.get(key); + if (canonical == null) { + var canonicalName = this.resolveCanonicalIdentifier( + availableRelations.values().stream().filter(ref -> ref.kind() == kind).map(ObjectRef::name).toList(), + requested.name(), + "Unknown relation" + ); + canonical = availableRelations.get(new RelationKey(canonicalSchema, canonicalName, kind)); + } + canonicalRequests.putIfAbsent(canonical, canonical); + } + return List.copyOf(canonicalRequests.keySet()); + } + + private Map loadRelationsByKey(MetadataCatalog catalog, String catalogName, String schema) + throws SQLException { + var result = new LinkedHashMap(); + for (var relation : catalog.listRelations(new RelationScope(catalogName, schema), COMPLETION_KINDS)) { + var ref = relation.ref(); + result.put(new RelationKey(schema, ref.name(), ref.kind()), ref); + } + return result; + } + + private void validateRequestedRelation(QualifiedRelation relation, String catalog) { + if (relation == null || StringUtils.isBlank(relation.name()) || !RELATION_KIND_STRINGS.contains(relation.kind())) { + throw new IllegalArgumentException("Invalid relation metadata request"); + } + if (StringUtils.isNotBlank(relation.catalog()) && StringUtils.isNotBlank(catalog) + && !relation.catalog().equals(catalog)) { + throw new IllegalArgumentException("Relation catalog does not match the active context"); + } + } + + private String resolveCanonicalIdentifier(Collection candidates, String requested, String unknownMessage) { + if (StringUtils.isBlank(requested)) { + throw new IllegalArgumentException(unknownMessage); + } + var exactMatches = candidates.stream().filter(requested::equals).distinct().toList(); + if (exactMatches.size() == 1) { + return exactMatches.get(0); + } + var caseRule = this.identifierCaseRule(); + if (caseRule == IdentifierCaseRule.LOWER || caseRule == IdentifierCaseRule.UPPER) { + var normalizedRequested = caseRule.normalize(requested); + var normalizedMatches = candidates.stream() + .filter(candidate -> candidate.equals(caseRule.normalize(candidate))) + .filter(candidate -> candidate.equals(normalizedRequested)) + .distinct() + .toList(); + if (normalizedMatches.size() == 1) { + return normalizedMatches.get(0); + } + throw new IllegalArgumentException(unknownMessage); + } + if (caseRule == IdentifierCaseRule.INSENSITIVE) { + var insensitiveMatches = candidates.stream() + .filter(candidate -> candidate.equalsIgnoreCase(requested)) + .distinct() + .toList(); + if (insensitiveMatches.size() == 1) { + return insensitiveMatches.get(0); + } + } + throw new IllegalArgumentException(unknownMessage); + } + + private IdentifierCaseRule identifierCaseRule() { + var connectInfo = SessionManager.getCurrentSession().getDatasource().getConnectInfo(); + var dbType = connectInfo == null ? "" : StringUtils.defaultString(connectInfo.getDbType()); + return switch (dbType.toLowerCase(Locale.ROOT)) { + case "postgresql" -> IdentifierCaseRule.LOWER; + case "oracle", "db2", "dm", "dameng" -> IdentifierCaseRule.UPPER; + case "mysql", "mariadb", "sqlserver" -> IdentifierCaseRule.INSENSITIVE; + default -> IdentifierCaseRule.EXACT; + }; + } + + private ResourceNodeSnapshot resolveNode(ResourceBrowser browser, String nodeKey) { + if (StringUtils.isBlank(nodeKey)) { + throw new ChenException("Invalid metadata context"); + } + var node = browser.getIndexedNode(nodeKey); + if (node == null || !QUERY_NODE_TYPES.contains(node.type())) { + throw new ChenException("Invalid metadata context"); + } + return node; + } + + private record RelationKey(String schema, String name, RelationKind kind) { + } + + private enum IdentifierCaseRule { + LOWER, + UPPER, + INSENSITIVE, + EXACT; + + private String normalize(String identifier) { + return this == UPPER ? identifier.toUpperCase(Locale.ROOT) : identifier.toLowerCase(Locale.ROOT); + } + } +} diff --git a/backend/web/src/test/java/org/jumpserver/chen/web/ai/SqlAgentToolServiceTest.java b/backend/web/src/test/java/org/jumpserver/chen/web/ai/SqlAgentToolServiceTest.java new file mode 100644 index 0000000..7d6a0d9 --- /dev/null +++ b/backend/web/src/test/java/org/jumpserver/chen/web/ai/SqlAgentToolServiceTest.java @@ -0,0 +1,126 @@ +package org.jumpserver.chen.web.ai; + +import com.alibaba.druid.DbType; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; +import org.jumpserver.chen.framework.console.Console; +import org.jumpserver.chen.framework.console.context.ConsoleContext; +import org.jumpserver.chen.framework.datasource.Datasource; +import org.jumpserver.chen.framework.datasource.ResourceBrowser; +import org.jumpserver.chen.framework.datasource.entity.DBConnectInfo; +import org.jumpserver.chen.framework.session.Session; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SqlAgentToolServiceTest { + @Test + void resolvesContextFromActiveConsoleWhenResourceIndexWasRefreshed() { + String nodeKey = "datasource:root,schema:public,folder:tables,table:settings_setting"; + var session = mock(Session.class); + var datasource = mock(Datasource.class); + var resourceBrowser = mock(ResourceBrowser.class); + var console = mock(Console.class); + var connectInfo = new DBConnectInfo(); + connectInfo.setDbType("postgresql"); + + when(session.isActive()).thenReturn(true); + when(session.getDatasource()).thenReturn(datasource); + when(session.getConsoles()).thenReturn(Map.of("console-1", console)); + when(datasource.getConnectInfo()).thenReturn(connectInfo); + when(datasource.getDruidDbType()).thenReturn(DbType.postgresql); + when(datasource.getResourceBrowser()).thenReturn(resourceBrowser); + when(resourceBrowser.getIndexedNode(nodeKey)).thenReturn(null); + when(console.getNodeKey()).thenReturn(nodeKey); + when(console.getContext()).thenReturn(new ConsoleContext( + nodeKey, "table", null, "public", "settings_setting" + )); + + var resolved = new SqlAgentToolService().resolveRequestContext(session, """ + { + "nodeKey": "%s", + "consoleId": "console-1", + "workspaceTabId": "query-1", + "workspaceTabKind": "query", + "documentSql": "SELECT 1;\\nSELECT id FROM settings_setting", + "selectionFrom": 10, + "selectionTo": 41 + } + """.formatted(nodeKey), "repair"); + + assertEquals(nodeKey, resolved.nodeKey()); + assertEquals("public", resolved.schema()); + assertEquals("settings_setting", resolved.table()); + var sanitized = JsonParser.parseString(resolved.sanitizedJson()).getAsJsonObject(); + assertEquals("", sanitized.get("documentSql").getAsString()); + assertEquals("SELECT id FROM settings_setting", sanitized.get("selectedSql").getAsString()); + assertEquals("settings_setting", + sanitized.getAsJsonArray("referencedTables").get(0).getAsString()); + assertTrue(sanitized.has("currentSqlAnalysis")); + } + + @Test + void validatesReadOnlySqlForEveryChenDialect() { + List dialects = List.of( + DbType.mysql, + DbType.mariadb, + DbType.postgresql, + DbType.sqlserver, + DbType.oracle, + DbType.clickhouse, + DbType.dm, + DbType.db2 + ); + + for (DbType dialect : dialects) { + Map result = SqlAgentToolService.validateSQL(dialect, "SELECT id FROM users"); + assertEquals(true, result.get("valid"), dialect.name()); + assertEquals(1, result.get("statementCount"), dialect.name()); + assertEquals(1, result.get("riskLevel"), dialect.name()); + } + } + + @Test + void reportsSyntaxErrorsWithoutExecutingSql() { + Map result = SqlAgentToolService.validateSQL(DbType.postgresql, "SELECT * FRM users"); + + assertEquals(false, result.get("valid")); + assertEquals(0, result.get("statementCount")); + assertFalse(((List) result.get("errors")).isEmpty()); + } + + @Test + void identifiesMultiStatementWriteRisk() { + Map result = SqlAgentToolService.validateSQL( + DbType.mysql, + "SELECT id FROM users; DELETE FROM users WHERE id = 1" + ); + + assertEquals(true, result.get("valid")); + assertEquals(2, result.get("statementCount")); + assertEquals("MULTI", result.get("statementType")); + assertTrue(((Number) result.get("riskLevel")).intValue() >= 3); + } + + @Test + void errorContextDropsUnknownBusinessData() { + var sanitized = SqlAgentToolService.sanitizeLastError(JsonParser.parseString(""" + { + "kind": "execute", + "message": "column does not exist", + "sql": "SELECT secret FROM users", + "rows": [{"secret": "must-not-leave-chen"}] + } + """)); + + assertEquals("execute", sanitized.get("kind").getAsString()); + assertEquals("column does not exist", sanitized.get("message").getAsString()); + assertFalse(sanitized.has("rows")); + } +} diff --git a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java index 21d8cc1..f7f8e25 100644 --- a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java +++ b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceGrpc.java @@ -4,15 +4,12 @@ /** */ -@javax.annotation.Generated( - value = "by gRPC proto compiler (version 1.56.1)", - comments = "Source: service.proto") @io.grpc.stub.annotations.GrpcGenerated public final class ServiceGrpc { private ServiceGrpc() {} - public static final String SERVICE_NAME = "message.Service"; + public static final java.lang.String SERVICE_NAME = "message.Service"; // Static method descriptors that strictly reflect the proto. private static volatile io.grpc.MethodDescriptor getCallAPIMethod() { return getCallAPIMethod; } + private static volatile io.grpc.MethodDescriptor getAgentSessionMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "AgentSession", + requestType = org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.class, + responseType = org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getAgentSessionMethod() { + io.grpc.MethodDescriptor getAgentSessionMethod; + if ((getAgentSessionMethod = ServiceGrpc.getAgentSessionMethod) == null) { + synchronized (ServiceGrpc.class) { + if ((getAgentSessionMethod = ServiceGrpc.getAgentSessionMethod) == null) { + ServiceGrpc.getAgentSessionMethod = getAgentSessionMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "AgentSession")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.getDefaultInstance())) + .setSchemaDescriptor(new ServiceMethodDescriptorSupplier("AgentSession")) + .build(); + } + } + } + return getAgentSessionMethod; + } + /** * Creates a new async stub that supports all call types for the service */ @@ -804,6 +832,21 @@ public ServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOpti return ServiceStub.newStub(factory, channel); } + /** + * Creates a new blocking-style stub that supports all types of calls on the service + */ + public static ServiceBlockingV2Stub newBlockingV2Stub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public ServiceBlockingV2Stub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new ServiceBlockingV2Stub(channel, callOptions); + } + }; + return ServiceBlockingV2Stub.newStub(factory, channel); + } + /** * Creates a new blocking-style stub that supports unary and streaming output calls on the service */ @@ -1012,6 +1055,13 @@ default void callAPI(org.jumpserver.wisp.ServiceOuterClass.HTTPRequest request, io.grpc.stub.StreamObserver responseObserver) { io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getCallAPIMethod(), responseObserver); } + + /** + */ + default io.grpc.stub.StreamObserver agentSession( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getAgentSessionMethod(), responseObserver); + } } /** @@ -1240,11 +1290,222 @@ public void callAPI(org.jumpserver.wisp.ServiceOuterClass.HTTPRequest request, io.grpc.stub.ClientCalls.asyncUnaryCall( getChannel().newCall(getCallAPIMethod(), getCallOptions()), request, responseObserver); } + + /** + */ + public io.grpc.stub.StreamObserver agentSession( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getAgentSessionMethod(), getCallOptions()), responseObserver); + } } /** * A stub to allow clients to do synchronous rpc calls to service Service. */ + public static final class ServiceBlockingV2Stub + extends io.grpc.stub.AbstractBlockingStub { + private ServiceBlockingV2Stub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected ServiceBlockingV2Stub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new ServiceBlockingV2Stub(channel, callOptions); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.TokenResponse getTokenAuthInfo(org.jumpserver.wisp.ServiceOuterClass.TokenRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetTokenAuthInfoMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse renewToken(org.jumpserver.wisp.ServiceOuterClass.TokenRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getRenewTokenMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.SessionCreateResponse createSession(org.jumpserver.wisp.ServiceOuterClass.SessionCreateRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCreateSessionMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.SessionFinishResp finishSession(org.jumpserver.wisp.ServiceOuterClass.SessionFinishRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getFinishSessionMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.ReplayResponse uploadReplayFile(org.jumpserver.wisp.ServiceOuterClass.ReplayRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUploadReplayFileMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.CommandResponse uploadCommand(org.jumpserver.wisp.ServiceOuterClass.CommandRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getUploadCommandMethod(), getCallOptions(), request); + } + + /** + */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall + dispatchTask() { + return io.grpc.stub.ClientCalls.blockingBidiStreamingCall( + getChannel(), getDispatchTaskMethod(), getCallOptions()); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.RemainReplayResponse scanRemainReplays(org.jumpserver.wisp.ServiceOuterClass.RemainReplayRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getScanRemainReplaysMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.CommandConfirmResponse createCommandTicket(org.jumpserver.wisp.ServiceOuterClass.CommandConfirmRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCreateCommandTicketMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.AssetLoginTicketResponse checkOrCreateAssetLoginTicket(org.jumpserver.wisp.ServiceOuterClass.AssetLoginTicketRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCheckOrCreateAssetLoginTicketMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.TicketStateResponse checkTicketState(org.jumpserver.wisp.ServiceOuterClass.TicketRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCheckTicketStateMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse cancelTicket(org.jumpserver.wisp.ServiceOuterClass.TicketRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCancelTicketMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.ForwardResponse createForward(org.jumpserver.wisp.ServiceOuterClass.ForwardRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCreateForwardMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse deleteForward(org.jumpserver.wisp.ServiceOuterClass.ForwardDeleteRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getDeleteForwardMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.PublicSettingResponse getPublicSetting(org.jumpserver.wisp.ServiceOuterClass.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetPublicSettingMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.ListenPortResponse getListenPorts(org.jumpserver.wisp.ServiceOuterClass.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetListenPortsMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.PortInfoResponse getPortInfo(org.jumpserver.wisp.ServiceOuterClass.PortInfoRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetPortInfoMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse handlePortFailure(org.jumpserver.wisp.ServiceOuterClass.PortFailureRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getHandlePortFailureMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.UserResponse checkUserByCookies(org.jumpserver.wisp.ServiceOuterClass.CookiesRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCheckUserByCookiesMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.StatusResponse recordSessionLifecycleLog(org.jumpserver.wisp.ServiceOuterClass.SessionLifecycleLogRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getRecordSessionLifecycleLogMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.FaceRecognitionCallbackResponse faceRecognitionCallback(org.jumpserver.wisp.ServiceOuterClass.FaceRecognitionCallbackRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getFaceRecognitionCallbackMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.FaceMonitorCallbackResponse faceMonitorCallback(org.jumpserver.wisp.ServiceOuterClass.FaceMonitorCallbackRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getFaceMonitorCallbackMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.JoinFaceMonitorResponse joinFaceMonitor(org.jumpserver.wisp.ServiceOuterClass.JoinFaceMonitorRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getJoinFaceMonitorMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.AccountDetailResponse getAccountChat(org.jumpserver.wisp.ServiceOuterClass.Empty request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getGetAccountChatMethod(), getCallOptions(), request); + } + + /** + */ + public org.jumpserver.wisp.ServiceOuterClass.HTTPResponse callAPI(org.jumpserver.wisp.ServiceOuterClass.HTTPRequest request) throws io.grpc.StatusException { + return io.grpc.stub.ClientCalls.blockingV2UnaryCall( + getChannel(), getCallAPIMethod(), getCallOptions(), request); + } + + /** + */ + @io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/10918") + public io.grpc.stub.BlockingClientCall + agentSession() { + return io.grpc.stub.ClientCalls.blockingBidiStreamingCall( + getChannel(), getAgentSessionMethod(), getCallOptions()); + } + } + + /** + * A stub to allow clients to do limited synchronous rpc calls to service Service. + */ public static final class ServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub { private ServiceBlockingStub( @@ -1661,6 +1922,7 @@ public com.google.common.util.concurrent.ListenableFuture implements io.grpc.stub.ServerCalls.UnaryMethod, @@ -1788,6 +2050,9 @@ public io.grpc.stub.StreamObserver invoke( case METHODID_DISPATCH_TASK: return (io.grpc.stub.StreamObserver) serviceImpl.dispatchTask( (io.grpc.stub.StreamObserver) responseObserver); + case METHODID_AGENT_SESSION: + return (io.grpc.stub.StreamObserver) serviceImpl.agentSession( + (io.grpc.stub.StreamObserver) responseObserver); default: throw new AssertionError(); } @@ -1971,6 +2236,13 @@ public static final io.grpc.ServerServiceDefinition bindService(AsyncService ser org.jumpserver.wisp.ServiceOuterClass.HTTPRequest, org.jumpserver.wisp.ServiceOuterClass.HTTPResponse>( service, METHODID_CALL_API))) + .addMethod( + getAgentSessionMethod(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent, + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent>( + service, METHODID_AGENT_SESSION))) .build(); } @@ -1997,9 +2269,9 @@ private static final class ServiceFileDescriptorSupplier private static final class ServiceMethodDescriptorSupplier extends ServiceBaseDescriptorSupplier implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { - private final String methodName; + private final java.lang.String methodName; - ServiceMethodDescriptorSupplier(String methodName) { + ServiceMethodDescriptorSupplier(java.lang.String methodName) { this.methodName = methodName; } @@ -2044,6 +2316,7 @@ public static io.grpc.ServiceDescriptor getServiceDescriptor() { .addMethod(getJoinFaceMonitorMethod()) .addMethod(getGetAccountChatMethod()) .addMethod(getCallAPIMethod()) + .addMethod(getAgentSessionMethod()) .build(); } } diff --git a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java index faebbfa..d146892 100644 --- a/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java +++ b/backend/wisp/src/main/java/org/jumpserver/wisp/ServiceOuterClass.java @@ -106,7 +106,7 @@ public java.lang.String getFaceMonitorToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); faceMonitorToken_ = s; @@ -122,7 +122,7 @@ public java.lang.String getFaceMonitorToken() { getFaceMonitorTokenBytes() { java.lang.Object ref = faceMonitorToken_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceMonitorToken_ = b; @@ -145,7 +145,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -161,7 +161,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -514,7 +514,7 @@ public java.lang.String getFaceMonitorToken() { getFaceMonitorTokenBytes() { java.lang.Object ref = faceMonitorToken_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceMonitorToken_ = b; @@ -586,7 +586,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -1190,7 +1190,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -1386,7 +1386,7 @@ public java.lang.String getToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); token_ = s; @@ -1402,7 +1402,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -1436,7 +1436,7 @@ public java.lang.String getErrorMessage() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); errorMessage_ = s; @@ -1452,7 +1452,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -1486,7 +1486,7 @@ public java.lang.String getAction() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); action_ = s; @@ -1502,7 +1502,7 @@ public java.lang.String getAction() { getActionBytes() { java.lang.Object ref = action_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); action_ = b; @@ -2003,7 +2003,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -2107,7 +2107,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -2211,7 +2211,7 @@ public java.lang.String getAction() { getActionBytes() { java.lang.Object ref = action_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); action_ = b; @@ -2926,7 +2926,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -3089,7 +3089,7 @@ public java.lang.String getToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); token_ = s; @@ -3105,7 +3105,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -3139,7 +3139,7 @@ public java.lang.String getErrorMessage() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); errorMessage_ = s; @@ -3155,7 +3155,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -3178,7 +3178,7 @@ public java.lang.String getFaceCode() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); faceCode_ = s; @@ -3194,7 +3194,7 @@ public java.lang.String getFaceCode() { getFaceCodeBytes() { java.lang.Object ref = faceCode_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceCode_ = b; @@ -3595,7 +3595,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -3699,7 +3699,7 @@ public java.lang.String getErrorMessage() { getErrorMessageBytes() { java.lang.Object ref = errorMessage_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); errorMessage_ = b; @@ -3771,7 +3771,7 @@ public java.lang.String getFaceCode() { getFaceCodeBytes() { java.lang.Object ref = faceCode_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); faceCode_ = b; @@ -4375,7 +4375,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -4532,7 +4532,7 @@ public java.lang.String getUserId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); userId_ = s; @@ -4548,7 +4548,7 @@ public java.lang.String getUserId() { getUserIdBytes() { java.lang.Object ref = userId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); userId_ = b; @@ -4571,7 +4571,7 @@ public java.lang.String getAssetId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); assetId_ = s; @@ -4587,7 +4587,7 @@ public java.lang.String getAssetId() { getAssetIdBytes() { java.lang.Object ref = assetId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); assetId_ = b; @@ -4610,7 +4610,7 @@ public java.lang.String getAccountUsername() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); accountUsername_ = s; @@ -4626,7 +4626,7 @@ public java.lang.String getAccountUsername() { getAccountUsernameBytes() { java.lang.Object ref = accountUsername_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); accountUsername_ = b; @@ -5003,7 +5003,7 @@ public java.lang.String getUserId() { getUserIdBytes() { java.lang.Object ref = userId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); userId_ = b; @@ -5075,7 +5075,7 @@ public java.lang.String getAssetId() { getAssetIdBytes() { java.lang.Object ref = assetId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); assetId_ = b; @@ -5147,7 +5147,7 @@ public java.lang.String getAccountUsername() { getAccountUsernameBytes() { java.lang.Object ref = accountUsername_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); accountUsername_ = b; @@ -5412,7 +5412,7 @@ public java.lang.String getTicketId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); ticketId_ = s; @@ -5428,7 +5428,7 @@ public java.lang.String getTicketId() { getTicketIdBytes() { java.lang.Object ref = ticketId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketId_ = b; @@ -5947,7 +5947,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -6068,7 +6068,7 @@ public org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder getTicketInfoOr * .message.TicketInfo ticket_info = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> internalGetTicketInfoFieldBuilder() { if (ticketInfoBuilder_ == null) { ticketInfoBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -6138,7 +6138,7 @@ public java.lang.String getTicketId() { getTicketIdBytes() { java.lang.Object ref = ticketId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketId_ = b; @@ -6320,7 +6320,7 @@ public java.lang.String getErr() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); err_ = s; @@ -6336,7 +6336,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -6721,7 +6721,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -6886,7 +6886,7 @@ public java.lang.String getToken() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); token_ = s; @@ -6902,7 +6902,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -7231,7 +7231,7 @@ public java.lang.String getToken() { getTokenBytes() { java.lang.Object ref = token_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); token_ = b; @@ -7914,7 +7914,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -8035,7 +8035,7 @@ public org.jumpserver.wisp.Common.TokenAuthInfoOrBuilder getDataOrBuilder() { * .message.TokenAuthInfo data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.TokenAuthInfo, org.jumpserver.wisp.Common.TokenAuthInfo.Builder, org.jumpserver.wisp.Common.TokenAuthInfoOrBuilder> + org.jumpserver.wisp.Common.TokenAuthInfo, org.jumpserver.wisp.Common.TokenAuthInfo.Builder, org.jumpserver.wisp.Common.TokenAuthInfoOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -8605,7 +8605,7 @@ public org.jumpserver.wisp.Common.SessionOrBuilder getDataOrBuilder() { * .message.Session data = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> + org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -9254,7 +9254,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -9375,7 +9375,7 @@ public org.jumpserver.wisp.Common.SessionOrBuilder getDataOrBuilder() { * .message.Session data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> + org.jumpserver.wisp.Common.Session, org.jumpserver.wisp.Common.Session.Builder, org.jumpserver.wisp.Common.SessionOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -9531,7 +9531,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -9547,7 +9547,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -9592,7 +9592,7 @@ public java.lang.String getErr() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); err_ = s; @@ -9608,7 +9608,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -10009,7 +10009,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -10145,7 +10145,7 @@ public java.lang.String getErr() { getErrBytes() { java.lang.Object ref = err_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); err_ = b; @@ -10749,7 +10749,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -10893,7 +10893,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -10909,7 +10909,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -10932,7 +10932,7 @@ public java.lang.String getReplayFilePath() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); replayFilePath_ = s; @@ -10948,7 +10948,7 @@ public java.lang.String getReplayFilePath() { getReplayFilePathBytes() { java.lang.Object ref = replayFilePath_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayFilePath_ = b; @@ -11301,7 +11301,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -11373,7 +11373,7 @@ public java.lang.String getReplayFilePath() { getReplayFilePathBytes() { java.lang.Object ref = replayFilePath_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayFilePath_ = b; @@ -11977,7 +11977,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -12230,7 +12230,7 @@ public java.lang.String getSid() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sid_ = s; @@ -12246,7 +12246,7 @@ public java.lang.String getSid() { getSidBytes() { java.lang.Object ref = sid_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sid_ = b; @@ -12269,7 +12269,7 @@ public java.lang.String getOrgId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); orgId_ = s; @@ -12285,7 +12285,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -12308,7 +12308,7 @@ public java.lang.String getInput() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); input_ = s; @@ -12324,7 +12324,7 @@ public java.lang.String getInput() { getInputBytes() { java.lang.Object ref = input_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); input_ = b; @@ -12347,7 +12347,7 @@ public java.lang.String getOutput() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); output_ = s; @@ -12363,7 +12363,7 @@ public java.lang.String getOutput() { getOutputBytes() { java.lang.Object ref = output_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); output_ = b; @@ -12386,7 +12386,7 @@ public java.lang.String getUser() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); user_ = s; @@ -12402,7 +12402,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -12425,7 +12425,7 @@ public java.lang.String getAsset() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); asset_ = s; @@ -12441,7 +12441,7 @@ public java.lang.String getAsset() { getAssetBytes() { java.lang.Object ref = asset_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); asset_ = b; @@ -12464,7 +12464,7 @@ public java.lang.String getAccount() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); account_ = s; @@ -12480,7 +12480,7 @@ public java.lang.String getAccount() { getAccountBytes() { java.lang.Object ref = account_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); account_ = b; @@ -12532,7 +12532,7 @@ public java.lang.String getCmdAclId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmdAclId_ = s; @@ -12548,7 +12548,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -12571,7 +12571,7 @@ public java.lang.String getCmdGroupId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmdGroupId_ = s; @@ -12587,7 +12587,7 @@ public java.lang.String getCmdGroupId() { getCmdGroupIdBytes() { java.lang.Object ref = cmdGroupId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdGroupId_ = b; @@ -13154,7 +13154,7 @@ public java.lang.String getSid() { getSidBytes() { java.lang.Object ref = sid_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sid_ = b; @@ -13226,7 +13226,7 @@ public java.lang.String getOrgId() { getOrgIdBytes() { java.lang.Object ref = orgId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); orgId_ = b; @@ -13298,7 +13298,7 @@ public java.lang.String getInput() { getInputBytes() { java.lang.Object ref = input_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); input_ = b; @@ -13370,7 +13370,7 @@ public java.lang.String getOutput() { getOutputBytes() { java.lang.Object ref = output_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); output_ = b; @@ -13442,7 +13442,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -13514,7 +13514,7 @@ public java.lang.String getAsset() { getAssetBytes() { java.lang.Object ref = asset_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); asset_ = b; @@ -13586,7 +13586,7 @@ public java.lang.String getAccount() { getAccountBytes() { java.lang.Object ref = account_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); account_ = b; @@ -13741,7 +13741,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -13813,7 +13813,7 @@ public java.lang.String getCmdGroupId() { getCmdGroupIdBytes() { java.lang.Object ref = cmdGroupId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdGroupId_ = b; @@ -14417,7 +14417,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -14548,7 +14548,7 @@ public java.lang.String getTaskId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); taskId_ = s; @@ -14564,7 +14564,7 @@ public java.lang.String getTaskId() { getTaskIdBytes() { java.lang.Object ref = taskId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); taskId_ = b; @@ -14893,7 +14893,7 @@ public java.lang.String getTaskId() { getTaskIdBytes() { java.lang.Object ref = taskId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); taskId_ = b; @@ -15497,7 +15497,7 @@ public org.jumpserver.wisp.Common.TerminalTaskOrBuilder getTaskOrBuilder() { * .message.TerminalTask task = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.TerminalTask, org.jumpserver.wisp.Common.TerminalTask.Builder, org.jumpserver.wisp.Common.TerminalTaskOrBuilder> + org.jumpserver.wisp.Common.TerminalTask, org.jumpserver.wisp.Common.TerminalTask.Builder, org.jumpserver.wisp.Common.TerminalTaskOrBuilder> internalGetTaskFieldBuilder() { if (taskBuilder_ == null) { taskBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -15628,7 +15628,7 @@ public java.lang.String getReplayDir() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); replayDir_ = s; @@ -15644,7 +15644,7 @@ public java.lang.String getReplayDir() { getReplayDirBytes() { java.lang.Object ref = replayDir_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayDir_ = b; @@ -15973,7 +15973,7 @@ public java.lang.String getReplayDir() { getReplayDirBytes() { java.lang.Object ref = replayDir_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); replayDir_ = b; @@ -16886,7 +16886,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -17789,7 +17789,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -17946,7 +17946,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -17962,7 +17962,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -17985,7 +17985,7 @@ public java.lang.String getCmdAclId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmdAclId_ = s; @@ -18001,7 +18001,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -18024,7 +18024,7 @@ public java.lang.String getCmd() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); cmd_ = s; @@ -18040,7 +18040,7 @@ public java.lang.String getCmd() { getCmdBytes() { java.lang.Object ref = cmd_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmd_ = b; @@ -18417,7 +18417,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -18489,7 +18489,7 @@ public java.lang.String getCmdAclId() { getCmdAclIdBytes() { java.lang.Object ref = cmdAclId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmdAclId_ = b; @@ -18561,7 +18561,7 @@ public java.lang.String getCmd() { getCmdBytes() { java.lang.Object ref = cmd_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); cmd_ = b; @@ -18739,7 +18739,7 @@ public java.lang.String getMethod() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); method_ = s; @@ -18755,7 +18755,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -18778,7 +18778,7 @@ public java.lang.String getUrl() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); url_ = s; @@ -18794,7 +18794,7 @@ public java.lang.String getUrl() { getUrlBytes() { java.lang.Object ref = url_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); url_ = b; @@ -19147,7 +19147,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -19219,7 +19219,7 @@ public java.lang.String getUrl() { getUrlBytes() { java.lang.Object ref = url_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); url_ = b; @@ -19902,7 +19902,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20023,7 +20023,7 @@ public org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder getInfoOrBuilde * .message.TicketInfo info = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.TicketInfo, org.jumpserver.wisp.ServiceOuterClass.TicketInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketInfoOrBuilder> internalGetInfoFieldBuilder() { if (infoBuilder_ == null) { infoBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20264,7 +20264,7 @@ public java.lang.String getTicketDetailUrl() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); ticketDetailUrl_ = s; @@ -20280,7 +20280,7 @@ public java.lang.String getTicketDetailUrl() { getTicketDetailUrlBytes() { java.lang.Object ref = ticketDetailUrl_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketDetailUrl_ = b; @@ -20851,7 +20851,7 @@ public org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder getCheckReqOrBuild * .message.ReqInfo check_req = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> internalGetCheckReqFieldBuilder() { if (checkReqBuilder_ == null) { checkReqBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -20972,7 +20972,7 @@ public org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder getCancelReqOrBuil * .message.ReqInfo cancel_req = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> internalGetCancelReqFieldBuilder() { if (cancelReqBuilder_ == null) { cancelReqBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -21010,7 +21010,7 @@ public java.lang.String getTicketDetailUrl() { getTicketDetailUrlBytes() { java.lang.Object ref = ticketDetailUrl_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); ticketDetailUrl_ = b; @@ -21725,7 +21725,7 @@ public org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder getReqOrBuilder() * .message.ReqInfo req = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.ReqInfo, org.jumpserver.wisp.ServiceOuterClass.ReqInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.ReqInfoOrBuilder> internalGetReqFieldBuilder() { if (reqBuilder_ == null) { reqBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -22374,7 +22374,7 @@ public org.jumpserver.wisp.ServiceOuterClass.TicketStateOrBuilder getDataOrBuild * .message.TicketState Data = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.TicketState, org.jumpserver.wisp.ServiceOuterClass.TicketState.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketStateOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.TicketState, org.jumpserver.wisp.ServiceOuterClass.TicketState.Builder, org.jumpserver.wisp.ServiceOuterClass.TicketStateOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -22495,7 +22495,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -22791,7 +22791,7 @@ public java.lang.String getProcessor() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); processor_ = s; @@ -22807,7 +22807,7 @@ public java.lang.String getProcessor() { getProcessorBytes() { java.lang.Object ref = processor_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); processor_ = b; @@ -23209,7 +23209,7 @@ public java.lang.String getProcessor() { getProcessorBytes() { java.lang.Object ref = processor_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); processor_ = b; @@ -23332,7 +23332,7 @@ public interface ForwardRequestOrBuilder extends /** * repeated .message.Gateway gateways = 3; */ - java.util.List + java.util.List getGatewaysList(); /** * repeated .message.Gateway gateways = 3; @@ -23345,7 +23345,7 @@ public interface ForwardRequestOrBuilder extends /** * repeated .message.Gateway gateways = 3; */ - java.util.List + java.util.List getGatewaysOrBuilderList(); /** * repeated .message.Gateway gateways = 3; @@ -23405,7 +23405,7 @@ public java.lang.String getHost() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); host_ = s; @@ -23421,7 +23421,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -23456,7 +23456,7 @@ public java.util.List getGatewaysList() { * repeated .message.Gateway gateways = 3; */ @java.lang.Override - public java.util.List + public java.util.List getGatewaysOrBuilderList() { return gateways_; } @@ -23798,7 +23798,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.ForwardRequest ot gatewaysBuilder_ = null; gateways_ = other.gateways_; bitField0_ = (bitField0_ & ~0x00000004); - gatewaysBuilder_ = + gatewaysBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetGatewaysFieldBuilder() : null; } else { @@ -23897,7 +23897,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -24171,7 +24171,7 @@ public org.jumpserver.wisp.Common.GatewayOrBuilder getGatewaysOrBuilder( /** * repeated .message.Gateway gateways = 3; */ - public java.util.List + public java.util.List getGatewaysOrBuilderList() { if (gatewaysBuilder_ != null) { return gatewaysBuilder_.getMessageOrBuilderList(); @@ -24197,12 +24197,12 @@ public org.jumpserver.wisp.Common.Gateway.Builder addGatewaysBuilder( /** * repeated .message.Gateway gateways = 3; */ - public java.util.List + public java.util.List getGatewaysBuilderList() { return internalGetGatewaysFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> + org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> internalGetGatewaysFieldBuilder() { if (gatewaysBuilder_ == null) { gatewaysBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -24334,7 +24334,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -24350,7 +24350,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -24679,7 +24679,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -24905,7 +24905,7 @@ public java.lang.String getId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); id_ = s; @@ -24921,7 +24921,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -24944,7 +24944,7 @@ public java.lang.String getHost() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); host_ = s; @@ -24960,7 +24960,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -25475,7 +25475,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -25513,7 +25513,7 @@ public java.lang.String getId() { getIdBytes() { java.lang.Object ref = id_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); id_ = b; @@ -25585,7 +25585,7 @@ public java.lang.String getHost() { getHostBytes() { java.lang.Object ref = host_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); host_ = b; @@ -26300,7 +26300,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -26421,7 +26421,7 @@ public org.jumpserver.wisp.Common.PublicSettingOrBuilder getDataOrBuilder() { * .message.PublicSetting data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.PublicSetting, org.jumpserver.wisp.Common.PublicSetting.Builder, org.jumpserver.wisp.Common.PublicSettingOrBuilder> + org.jumpserver.wisp.Common.PublicSetting, org.jumpserver.wisp.Common.PublicSetting.Builder, org.jumpserver.wisp.Common.PublicSettingOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -27454,7 +27454,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -28621,7 +28621,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -28742,7 +28742,7 @@ public org.jumpserver.wisp.ServiceOuterClass.PortInfoOrBuilder getDataOrBuilder( * .message.PortInfo data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.PortInfo, org.jumpserver.wisp.ServiceOuterClass.PortInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.PortInfoOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.PortInfo, org.jumpserver.wisp.ServiceOuterClass.PortInfo.Builder, org.jumpserver.wisp.ServiceOuterClass.PortInfoOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -28828,7 +28828,7 @@ public interface PortInfoOrBuilder extends /** * repeated .message.Gateway gateways = 2; */ - java.util.List + java.util.List getGatewaysList(); /** * repeated .message.Gateway gateways = 2; @@ -28841,7 +28841,7 @@ public interface PortInfoOrBuilder extends /** * repeated .message.Gateway gateways = 2; */ - java.util.List + java.util.List getGatewaysOrBuilderList(); /** * repeated .message.Gateway gateways = 2; @@ -28928,7 +28928,7 @@ public java.util.List getGatewaysList() { * repeated .message.Gateway gateways = 2; */ @java.lang.Override - public java.util.List + public java.util.List getGatewaysOrBuilderList() { return gateways_; } @@ -29272,7 +29272,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.PortInfo other) { gatewaysBuilder_ = null; gateways_ = other.gateways_; bitField0_ = (bitField0_ & ~0x00000002); - gatewaysBuilder_ = + gatewaysBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetGatewaysFieldBuilder() : null; } else { @@ -29451,7 +29451,7 @@ public org.jumpserver.wisp.Common.AssetOrBuilder getAssetOrBuilder() { * .message.Asset asset = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.Asset, org.jumpserver.wisp.Common.Asset.Builder, org.jumpserver.wisp.Common.AssetOrBuilder> + org.jumpserver.wisp.Common.Asset, org.jumpserver.wisp.Common.Asset.Builder, org.jumpserver.wisp.Common.AssetOrBuilder> internalGetAssetFieldBuilder() { if (assetBuilder_ == null) { assetBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -29659,7 +29659,7 @@ public org.jumpserver.wisp.Common.GatewayOrBuilder getGatewaysOrBuilder( /** * repeated .message.Gateway gateways = 2; */ - public java.util.List + public java.util.List getGatewaysOrBuilderList() { if (gatewaysBuilder_ != null) { return gatewaysBuilder_.getMessageOrBuilderList(); @@ -29685,12 +29685,12 @@ public org.jumpserver.wisp.Common.Gateway.Builder addGatewaysBuilder( /** * repeated .message.Gateway gateways = 2; */ - public java.util.List + public java.util.List getGatewaysBuilderList() { return internalGetGatewaysFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> + org.jumpserver.wisp.Common.Gateway, org.jumpserver.wisp.Common.Gateway.Builder, org.jumpserver.wisp.Common.GatewayOrBuilder> internalGetGatewaysFieldBuilder() { if (gatewaysBuilder_ == null) { gatewaysBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -29839,7 +29839,7 @@ public java.lang.String getReason() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); reason_ = s; @@ -29855,7 +29855,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -30239,7 +30239,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -30344,7 +30344,7 @@ public interface PortFailureRequestOrBuilder extends /** * repeated .message.PortFailure data = 1; */ - java.util.List + java.util.List getDataList(); /** * repeated .message.PortFailure data = 1; @@ -30357,7 +30357,7 @@ public interface PortFailureRequestOrBuilder extends /** * repeated .message.PortFailure data = 1; */ - java.util.List + java.util.List getDataOrBuilderList(); /** * repeated .message.PortFailure data = 1; @@ -30417,7 +30417,7 @@ public java.util.List getData * repeated .message.PortFailure data = 1; */ @java.lang.Override - public java.util.List + public java.util.List getDataOrBuilderList() { return data_; } @@ -30722,7 +30722,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.PortFailureReques dataBuilder_ = null; data_ = other.data_; bitField0_ = (bitField0_ & ~0x00000001); - dataBuilder_ = + dataBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetDataFieldBuilder() : null; } else { @@ -30981,7 +30981,7 @@ public org.jumpserver.wisp.ServiceOuterClass.PortFailureOrBuilder getDataOrBuild /** * repeated .message.PortFailure data = 1; */ - public java.util.List + public java.util.List getDataOrBuilderList() { if (dataBuilder_ != null) { return dataBuilder_.getMessageOrBuilderList(); @@ -31007,12 +31007,12 @@ public org.jumpserver.wisp.ServiceOuterClass.PortFailure.Builder addDataBuilder( /** * repeated .message.PortFailure data = 1; */ - public java.util.List + public java.util.List getDataBuilderList() { return internalGetDataFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.PortFailure, org.jumpserver.wisp.ServiceOuterClass.PortFailure.Builder, org.jumpserver.wisp.ServiceOuterClass.PortFailureOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.PortFailure, org.jumpserver.wisp.ServiceOuterClass.PortFailure.Builder, org.jumpserver.wisp.ServiceOuterClass.PortFailureOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -31084,7 +31084,7 @@ public interface CookiesRequestOrBuilder extends /** * repeated .message.Cookie cookies = 1; */ - java.util.List + java.util.List getCookiesList(); /** * repeated .message.Cookie cookies = 1; @@ -31097,7 +31097,7 @@ public interface CookiesRequestOrBuilder extends /** * repeated .message.Cookie cookies = 1; */ - java.util.List + java.util.List getCookiesOrBuilderList(); /** * repeated .message.Cookie cookies = 1; @@ -31157,7 +31157,7 @@ public java.util.List getCookiesList() { * repeated .message.Cookie cookies = 1; */ @java.lang.Override - public java.util.List + public java.util.List getCookiesOrBuilderList() { return cookies_; } @@ -31462,7 +31462,7 @@ public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.CookiesRequest ot cookiesBuilder_ = null; cookies_ = other.cookies_; bitField0_ = (bitField0_ & ~0x00000001); - cookiesBuilder_ = + cookiesBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? internalGetCookiesFieldBuilder() : null; } else { @@ -31721,7 +31721,7 @@ public org.jumpserver.wisp.Common.CookieOrBuilder getCookiesOrBuilder( /** * repeated .message.Cookie cookies = 1; */ - public java.util.List + public java.util.List getCookiesOrBuilderList() { if (cookiesBuilder_ != null) { return cookiesBuilder_.getMessageOrBuilderList(); @@ -31747,12 +31747,12 @@ public org.jumpserver.wisp.Common.Cookie.Builder addCookiesBuilder( /** * repeated .message.Cookie cookies = 1; */ - public java.util.List + public java.util.List getCookiesBuilderList() { return internalGetCookiesFieldBuilder().getBuilderList(); } private com.google.protobuf.RepeatedFieldBuilder< - org.jumpserver.wisp.Common.Cookie, org.jumpserver.wisp.Common.Cookie.Builder, org.jumpserver.wisp.Common.CookieOrBuilder> + org.jumpserver.wisp.Common.Cookie, org.jumpserver.wisp.Common.Cookie.Builder, org.jumpserver.wisp.Common.CookieOrBuilder> internalGetCookiesFieldBuilder() { if (cookiesBuilder_ == null) { cookiesBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< @@ -32402,7 +32402,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -32523,7 +32523,7 @@ public org.jumpserver.wisp.Common.UserOrBuilder getDataOrBuilder() { * .message.User data = 2; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.Common.User, org.jumpserver.wisp.Common.User.Builder, org.jumpserver.wisp.Common.UserOrBuilder> + org.jumpserver.wisp.Common.User, org.jumpserver.wisp.Common.User.Builder, org.jumpserver.wisp.Common.UserOrBuilder> internalGetDataFieldBuilder() { if (dataBuilder_ == null) { dataBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -32908,7 +32908,7 @@ public java.lang.String getSessionId() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); sessionId_ = s; @@ -32924,7 +32924,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -32965,7 +32965,7 @@ public java.lang.String getReason() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); reason_ = s; @@ -32981,7 +32981,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -33004,7 +33004,7 @@ public java.lang.String getUser() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); user_ = s; @@ -33020,7 +33020,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -33419,7 +33419,7 @@ public java.lang.String getSessionId() { getSessionIdBytes() { java.lang.Object ref = sessionId_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); sessionId_ = b; @@ -33542,7 +33542,7 @@ public java.lang.String getReason() { getReasonBytes() { java.lang.Object ref = reason_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); reason_ = b; @@ -33614,7 +33614,7 @@ public java.lang.String getUser() { getUserBytes() { java.lang.Object ref = user_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); user_ = b; @@ -34297,7 +34297,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -34418,7 +34418,7 @@ public com.google.protobuf.StructOrBuilder getPayloadOrBuilder() { * .google.protobuf.Struct payload = 2; */ private com.google.protobuf.SingleFieldBuilder< - com.google.protobuf.Struct, com.google.protobuf.Struct.Builder, com.google.protobuf.StructOrBuilder> + com.google.protobuf.Struct, com.google.protobuf.Struct.Builder, com.google.protobuf.StructOrBuilder> internalGetPayloadFieldBuilder() { if (payloadBuilder_ == null) { payloadBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -34651,7 +34651,7 @@ public java.lang.String getMethod() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); method_ = s; @@ -34667,7 +34667,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -34690,7 +34690,7 @@ public java.lang.String getPath() { if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { - com.google.protobuf.ByteString bs = + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); path_ = s; @@ -34706,7 +34706,7 @@ public java.lang.String getPath() { getPathBytes() { java.lang.Object ref = path_; if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); path_ = b; @@ -34722,7 +34722,7 @@ private static final class QueryDefaultEntryHolder { java.lang.String, java.lang.String> defaultEntry = com.google.protobuf.MapEntry .newDefaultInstance( - org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_QueryEntry_descriptor, + org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_QueryEntry_descriptor, com.google.protobuf.WireFormat.FieldType.STRING, "", com.google.protobuf.WireFormat.FieldType.STRING, @@ -34801,7 +34801,7 @@ private static final class HeaderDefaultEntryHolder { java.lang.String, java.lang.String> defaultEntry = com.google.protobuf.MapEntry .newDefaultInstance( - org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_HeaderEntry_descriptor, + org.jumpserver.wisp.ServiceOuterClass.internal_static_message_HTTPRequest_HeaderEntry_descriptor, com.google.protobuf.WireFormat.FieldType.STRING, "", com.google.protobuf.WireFormat.FieldType.STRING, @@ -35355,7 +35355,7 @@ public java.lang.String getMethod() { getMethodBytes() { java.lang.Object ref = method_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); method_ = b; @@ -35427,7 +35427,7 @@ public java.lang.String getPath() { getPathBytes() { java.lang.Object ref = path_; if (ref instanceof String) { - com.google.protobuf.ByteString b = + com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); path_ = b; @@ -36358,7 +36358,7 @@ public org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder getStatusOrBuilder( * .message.Status status = 1; */ private com.google.protobuf.SingleFieldBuilder< - org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> + org.jumpserver.wisp.ServiceOuterClass.Status, org.jumpserver.wisp.ServiceOuterClass.Status.Builder, org.jumpserver.wisp.ServiceOuterClass.StatusOrBuilder> internalGetStatusFieldBuilder() { if (statusBuilder_ == null) { statusBuilder_ = new com.google.protobuf.SingleFieldBuilder< @@ -36454,256 +36454,10318 @@ public org.jumpserver.wisp.ServiceOuterClass.HTTPResponse getDefaultInstanceForT } + public interface AgentSessionOpenOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentSessionOpen) + com.google.protobuf.MessageOrBuilder { + + /** + * string session_id = 1; + * @return The sessionId. + */ + java.lang.String getSessionId(); + /** + * string session_id = 1; + * @return The bytes for sessionId. + */ + com.google.protobuf.ByteString + getSessionIdBytes(); + + /** + * string user_id = 2; + * @return The userId. + */ + java.lang.String getUserId(); + /** + * string user_id = 2; + * @return The bytes for userId. + */ + com.google.protobuf.ByteString + getUserIdBytes(); + + /** + * string organization_id = 3; + * @return The organizationId. + */ + java.lang.String getOrganizationId(); + /** + * string organization_id = 3; + * @return The bytes for organizationId. + */ + com.google.protobuf.ByteString + getOrganizationIdBytes(); + + /** + * string asset_id = 4; + * @return The assetId. + */ + java.lang.String getAssetId(); + /** + * string asset_id = 4; + * @return The bytes for assetId. + */ + com.google.protobuf.ByteString + getAssetIdBytes(); + + /** + * string account_id = 5; + * @return The accountId. + */ + java.lang.String getAccountId(); + /** + * string account_id = 5; + * @return The bytes for accountId. + */ + com.google.protobuf.ByteString + getAccountIdBytes(); + + /** + * string protocol = 6; + * @return The protocol. + */ + java.lang.String getProtocol(); + /** + * string protocol = 6; + * @return The bytes for protocol. + */ + com.google.protobuf.ByteString + getProtocolBytes(); + + /** + * string language = 7; + * @return The language. + */ + java.lang.String getLanguage(); + /** + * string language = 7; + * @return The bytes for language. + */ + com.google.protobuf.ByteString + getLanguageBytes(); + + /** + * string surface = 8; + * @return The surface. + */ + java.lang.String getSurface(); + /** + * string surface = 8; + * @return The bytes for surface. + */ + com.google.protobuf.ByteString + getSurfaceBytes(); + } + /** + *
+   * AgentSession is a component-to-Wisp session stream. Each Chen JMS session
+   * owns one independent stream while all streams share the existing HTTP/2
+   * channel. JSON payloads deliberately keep the UI chat and surface context
+   * independently evolvable from this transport contract.
+   * 
+ * + * Protobuf type {@code message.AgentSessionOpen} + */ + public static final class AgentSessionOpen extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentSessionOpen) + AgentSessionOpenOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentSessionOpen.class.getName()); + } + // Use AgentSessionOpen.newBuilder() to construct. + private AgentSessionOpen(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentSessionOpen() { + sessionId_ = ""; + userId_ = ""; + organizationId_ = ""; + assetId_ = ""; + accountId_ = ""; + protocol_ = ""; + language_ = ""; + surface_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentSessionOpen_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentSessionOpen_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.class, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.Builder.class); + } + + public static final int SESSION_ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object sessionId_ = ""; + /** + * string session_id = 1; + * @return The sessionId. + */ + @java.lang.Override + public java.lang.String getSessionId() { + java.lang.Object ref = sessionId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + sessionId_ = s; + return s; + } + } + /** + * string session_id = 1; + * @return The bytes for sessionId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getSessionIdBytes() { + java.lang.Object ref = sessionId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sessionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int USER_ID_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object userId_ = ""; + /** + * string user_id = 2; + * @return The userId. + */ + @java.lang.Override + public java.lang.String getUserId() { + java.lang.Object ref = userId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + userId_ = s; + return s; + } + } + /** + * string user_id = 2; + * @return The bytes for userId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getUserIdBytes() { + java.lang.Object ref = userId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + userId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ORGANIZATION_ID_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object organizationId_ = ""; + /** + * string organization_id = 3; + * @return The organizationId. + */ + @java.lang.Override + public java.lang.String getOrganizationId() { + java.lang.Object ref = organizationId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + organizationId_ = s; + return s; + } + } + /** + * string organization_id = 3; + * @return The bytes for organizationId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getOrganizationIdBytes() { + java.lang.Object ref = organizationId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + organizationId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ASSET_ID_FIELD_NUMBER = 4; + @SuppressWarnings("serial") + private volatile java.lang.Object assetId_ = ""; + /** + * string asset_id = 4; + * @return The assetId. + */ + @java.lang.Override + public java.lang.String getAssetId() { + java.lang.Object ref = assetId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + assetId_ = s; + return s; + } + } + /** + * string asset_id = 4; + * @return The bytes for assetId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getAssetIdBytes() { + java.lang.Object ref = assetId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + assetId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ACCOUNT_ID_FIELD_NUMBER = 5; + @SuppressWarnings("serial") + private volatile java.lang.Object accountId_ = ""; + /** + * string account_id = 5; + * @return The accountId. + */ + @java.lang.Override + public java.lang.String getAccountId() { + java.lang.Object ref = accountId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + accountId_ = s; + return s; + } + } + /** + * string account_id = 5; + * @return The bytes for accountId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getAccountIdBytes() { + java.lang.Object ref = accountId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + accountId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PROTOCOL_FIELD_NUMBER = 6; + @SuppressWarnings("serial") + private volatile java.lang.Object protocol_ = ""; + /** + * string protocol = 6; + * @return The protocol. + */ + @java.lang.Override + public java.lang.String getProtocol() { + java.lang.Object ref = protocol_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + protocol_ = s; + return s; + } + } + /** + * string protocol = 6; + * @return The bytes for protocol. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getProtocolBytes() { + java.lang.Object ref = protocol_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + protocol_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int LANGUAGE_FIELD_NUMBER = 7; + @SuppressWarnings("serial") + private volatile java.lang.Object language_ = ""; + /** + * string language = 7; + * @return The language. + */ + @java.lang.Override + public java.lang.String getLanguage() { + java.lang.Object ref = language_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + language_ = s; + return s; + } + } + /** + * string language = 7; + * @return The bytes for language. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getLanguageBytes() { + java.lang.Object ref = language_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + language_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int SURFACE_FIELD_NUMBER = 8; + @SuppressWarnings("serial") + private volatile java.lang.Object surface_ = ""; + /** + * string surface = 8; + * @return The surface. + */ + @java.lang.Override + public java.lang.String getSurface() { + java.lang.Object ref = surface_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + surface_ = s; + return s; + } + } + /** + * string surface = 8; + * @return The bytes for surface. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getSurfaceBytes() { + java.lang.Object ref = surface_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + surface_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(sessionId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, sessionId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(userId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, userId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(organizationId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, organizationId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(assetId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 4, assetId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(accountId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 5, accountId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(protocol_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 6, protocol_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(language_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 7, language_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(surface_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 8, surface_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(sessionId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, sessionId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(userId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, userId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(organizationId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, organizationId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(assetId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(4, assetId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(accountId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(5, accountId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(protocol_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(6, protocol_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(language_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(7, language_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(surface_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(8, surface_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen other = (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) obj; + + if (!getSessionId() + .equals(other.getSessionId())) return false; + if (!getUserId() + .equals(other.getUserId())) return false; + if (!getOrganizationId() + .equals(other.getOrganizationId())) return false; + if (!getAssetId() + .equals(other.getAssetId())) return false; + if (!getAccountId() + .equals(other.getAccountId())) return false; + if (!getProtocol() + .equals(other.getProtocol())) return false; + if (!getLanguage() + .equals(other.getLanguage())) return false; + if (!getSurface() + .equals(other.getSurface())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + SESSION_ID_FIELD_NUMBER; + hash = (53 * hash) + getSessionId().hashCode(); + hash = (37 * hash) + USER_ID_FIELD_NUMBER; + hash = (53 * hash) + getUserId().hashCode(); + hash = (37 * hash) + ORGANIZATION_ID_FIELD_NUMBER; + hash = (53 * hash) + getOrganizationId().hashCode(); + hash = (37 * hash) + ASSET_ID_FIELD_NUMBER; + hash = (53 * hash) + getAssetId().hashCode(); + hash = (37 * hash) + ACCOUNT_ID_FIELD_NUMBER; + hash = (53 * hash) + getAccountId().hashCode(); + hash = (37 * hash) + PROTOCOL_FIELD_NUMBER; + hash = (53 * hash) + getProtocol().hashCode(); + hash = (37 * hash) + LANGUAGE_FIELD_NUMBER; + hash = (53 * hash) + getLanguage().hashCode(); + hash = (37 * hash) + SURFACE_FIELD_NUMBER; + hash = (53 * hash) + getSurface().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+     * AgentSession is a component-to-Wisp session stream. Each Chen JMS session
+     * owns one independent stream while all streams share the existing HTTP/2
+     * channel. JSON payloads deliberately keep the UI chat and surface context
+     * independently evolvable from this transport contract.
+     * 
+ * + * Protobuf type {@code message.AgentSessionOpen} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentSessionOpen) + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpenOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentSessionOpen_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentSessionOpen_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.class, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + sessionId_ = ""; + userId_ = ""; + organizationId_ = ""; + assetId_ = ""; + accountId_ = ""; + protocol_ = ""; + language_ = ""; + surface_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentSessionOpen_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen build() { + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen result = new org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.sessionId_ = sessionId_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.userId_ = userId_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.organizationId_ = organizationId_; + } + if (((from_bitField0_ & 0x00000008) != 0)) { + result.assetId_ = assetId_; + } + if (((from_bitField0_ & 0x00000010) != 0)) { + result.accountId_ = accountId_; + } + if (((from_bitField0_ & 0x00000020) != 0)) { + result.protocol_ = protocol_; + } + if (((from_bitField0_ & 0x00000040) != 0)) { + result.language_ = language_; + } + if (((from_bitField0_ & 0x00000080) != 0)) { + result.surface_ = surface_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance()) return this; + if (!other.getSessionId().isEmpty()) { + sessionId_ = other.sessionId_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (!other.getUserId().isEmpty()) { + userId_ = other.userId_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (!other.getOrganizationId().isEmpty()) { + organizationId_ = other.organizationId_; + bitField0_ |= 0x00000004; + onChanged(); + } + if (!other.getAssetId().isEmpty()) { + assetId_ = other.assetId_; + bitField0_ |= 0x00000008; + onChanged(); + } + if (!other.getAccountId().isEmpty()) { + accountId_ = other.accountId_; + bitField0_ |= 0x00000010; + onChanged(); + } + if (!other.getProtocol().isEmpty()) { + protocol_ = other.protocol_; + bitField0_ |= 0x00000020; + onChanged(); + } + if (!other.getLanguage().isEmpty()) { + language_ = other.language_; + bitField0_ |= 0x00000040; + onChanged(); + } + if (!other.getSurface().isEmpty()) { + surface_ = other.surface_; + bitField0_ |= 0x00000080; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + sessionId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + userId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + organizationId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 26 + case 34: { + assetId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000008; + break; + } // case 34 + case 42: { + accountId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000010; + break; + } // case 42 + case 50: { + protocol_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000020; + break; + } // case 50 + case 58: { + language_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000040; + break; + } // case 58 + case 66: { + surface_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000080; + break; + } // case 66 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object sessionId_ = ""; + /** + * string session_id = 1; + * @return The sessionId. + */ + public java.lang.String getSessionId() { + java.lang.Object ref = sessionId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + sessionId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string session_id = 1; + * @return The bytes for sessionId. + */ + public com.google.protobuf.ByteString + getSessionIdBytes() { + java.lang.Object ref = sessionId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sessionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string session_id = 1; + * @param value The sessionId to set. + * @return This builder for chaining. + */ + public Builder setSessionId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + sessionId_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string session_id = 1; + * @return This builder for chaining. + */ + public Builder clearSessionId() { + sessionId_ = getDefaultInstance().getSessionId(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string session_id = 1; + * @param value The bytes for sessionId to set. + * @return This builder for chaining. + */ + public Builder setSessionIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + sessionId_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private java.lang.Object userId_ = ""; + /** + * string user_id = 2; + * @return The userId. + */ + public java.lang.String getUserId() { + java.lang.Object ref = userId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + userId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string user_id = 2; + * @return The bytes for userId. + */ + public com.google.protobuf.ByteString + getUserIdBytes() { + java.lang.Object ref = userId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + userId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string user_id = 2; + * @param value The userId to set. + * @return This builder for chaining. + */ + public Builder setUserId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + userId_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * string user_id = 2; + * @return This builder for chaining. + */ + public Builder clearUserId() { + userId_ = getDefaultInstance().getUserId(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * string user_id = 2; + * @param value The bytes for userId to set. + * @return This builder for chaining. + */ + public Builder setUserIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + userId_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private java.lang.Object organizationId_ = ""; + /** + * string organization_id = 3; + * @return The organizationId. + */ + public java.lang.String getOrganizationId() { + java.lang.Object ref = organizationId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + organizationId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string organization_id = 3; + * @return The bytes for organizationId. + */ + public com.google.protobuf.ByteString + getOrganizationIdBytes() { + java.lang.Object ref = organizationId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + organizationId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string organization_id = 3; + * @param value The organizationId to set. + * @return This builder for chaining. + */ + public Builder setOrganizationId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + organizationId_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * string organization_id = 3; + * @return This builder for chaining. + */ + public Builder clearOrganizationId() { + organizationId_ = getDefaultInstance().getOrganizationId(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + /** + * string organization_id = 3; + * @param value The bytes for organizationId to set. + * @return This builder for chaining. + */ + public Builder setOrganizationIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + organizationId_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + private java.lang.Object assetId_ = ""; + /** + * string asset_id = 4; + * @return The assetId. + */ + public java.lang.String getAssetId() { + java.lang.Object ref = assetId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + assetId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string asset_id = 4; + * @return The bytes for assetId. + */ + public com.google.protobuf.ByteString + getAssetIdBytes() { + java.lang.Object ref = assetId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + assetId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string asset_id = 4; + * @param value The assetId to set. + * @return This builder for chaining. + */ + public Builder setAssetId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + assetId_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + /** + * string asset_id = 4; + * @return This builder for chaining. + */ + public Builder clearAssetId() { + assetId_ = getDefaultInstance().getAssetId(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + /** + * string asset_id = 4; + * @param value The bytes for assetId to set. + * @return This builder for chaining. + */ + public Builder setAssetIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + assetId_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + private java.lang.Object accountId_ = ""; + /** + * string account_id = 5; + * @return The accountId. + */ + public java.lang.String getAccountId() { + java.lang.Object ref = accountId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + accountId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string account_id = 5; + * @return The bytes for accountId. + */ + public com.google.protobuf.ByteString + getAccountIdBytes() { + java.lang.Object ref = accountId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + accountId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string account_id = 5; + * @param value The accountId to set. + * @return This builder for chaining. + */ + public Builder setAccountId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + accountId_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; + } + /** + * string account_id = 5; + * @return This builder for chaining. + */ + public Builder clearAccountId() { + accountId_ = getDefaultInstance().getAccountId(); + bitField0_ = (bitField0_ & ~0x00000010); + onChanged(); + return this; + } + /** + * string account_id = 5; + * @param value The bytes for accountId to set. + * @return This builder for chaining. + */ + public Builder setAccountIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + accountId_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; + } + + private java.lang.Object protocol_ = ""; + /** + * string protocol = 6; + * @return The protocol. + */ + public java.lang.String getProtocol() { + java.lang.Object ref = protocol_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + protocol_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string protocol = 6; + * @return The bytes for protocol. + */ + public com.google.protobuf.ByteString + getProtocolBytes() { + java.lang.Object ref = protocol_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + protocol_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string protocol = 6; + * @param value The protocol to set. + * @return This builder for chaining. + */ + public Builder setProtocol( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + protocol_ = value; + bitField0_ |= 0x00000020; + onChanged(); + return this; + } + /** + * string protocol = 6; + * @return This builder for chaining. + */ + public Builder clearProtocol() { + protocol_ = getDefaultInstance().getProtocol(); + bitField0_ = (bitField0_ & ~0x00000020); + onChanged(); + return this; + } + /** + * string protocol = 6; + * @param value The bytes for protocol to set. + * @return This builder for chaining. + */ + public Builder setProtocolBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + protocol_ = value; + bitField0_ |= 0x00000020; + onChanged(); + return this; + } + + private java.lang.Object language_ = ""; + /** + * string language = 7; + * @return The language. + */ + public java.lang.String getLanguage() { + java.lang.Object ref = language_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + language_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string language = 7; + * @return The bytes for language. + */ + public com.google.protobuf.ByteString + getLanguageBytes() { + java.lang.Object ref = language_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + language_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string language = 7; + * @param value The language to set. + * @return This builder for chaining. + */ + public Builder setLanguage( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + language_ = value; + bitField0_ |= 0x00000040; + onChanged(); + return this; + } + /** + * string language = 7; + * @return This builder for chaining. + */ + public Builder clearLanguage() { + language_ = getDefaultInstance().getLanguage(); + bitField0_ = (bitField0_ & ~0x00000040); + onChanged(); + return this; + } + /** + * string language = 7; + * @param value The bytes for language to set. + * @return This builder for chaining. + */ + public Builder setLanguageBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + language_ = value; + bitField0_ |= 0x00000040; + onChanged(); + return this; + } + + private java.lang.Object surface_ = ""; + /** + * string surface = 8; + * @return The surface. + */ + public java.lang.String getSurface() { + java.lang.Object ref = surface_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + surface_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string surface = 8; + * @return The bytes for surface. + */ + public com.google.protobuf.ByteString + getSurfaceBytes() { + java.lang.Object ref = surface_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + surface_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string surface = 8; + * @param value The surface to set. + * @return This builder for chaining. + */ + public Builder setSurface( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + surface_ = value; + bitField0_ |= 0x00000080; + onChanged(); + return this; + } + /** + * string surface = 8; + * @return This builder for chaining. + */ + public Builder clearSurface() { + surface_ = getDefaultInstance().getSurface(); + bitField0_ = (bitField0_ & ~0x00000080); + onChanged(); + return this; + } + /** + * string surface = 8; + * @param value The bytes for surface to set. + * @return This builder for chaining. + */ + public Builder setSurfaceBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + surface_ = value; + bitField0_ |= 0x00000080; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentSessionOpen) + } + + // @@protoc_insertion_point(class_scope:message.AgentSessionOpen) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentSessionOpen parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentRequestOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentRequest) + com.google.protobuf.MessageOrBuilder { + + /** + * string id = 1; + * @return The id. + */ + java.lang.String getId(); + /** + * string id = 1; + * @return The bytes for id. + */ + com.google.protobuf.ByteString + getIdBytes(); + + /** + * string operation = 2; + * @return The operation. + */ + java.lang.String getOperation(); + /** + * string operation = 2; + * @return The bytes for operation. + */ + com.google.protobuf.ByteString + getOperationBytes(); + + /** + * string question = 3; + * @return The question. + */ + java.lang.String getQuestion(); + /** + * string question = 3; + * @return The bytes for question. + */ + com.google.protobuf.ByteString + getQuestionBytes(); + + /** + * string context_json = 4; + * @return The contextJson. + */ + java.lang.String getContextJson(); + /** + * string context_json = 4; + * @return The bytes for contextJson. + */ + com.google.protobuf.ByteString + getContextJsonBytes(); + } + /** + * Protobuf type {@code message.AgentRequest} + */ + public static final class AgentRequest extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentRequest) + AgentRequestOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentRequest.class.getName()); + } + // Use AgentRequest.newBuilder() to construct. + private AgentRequest(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentRequest() { + id_ = ""; + operation_ = ""; + question_ = ""; + contextJson_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentRequest_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentRequest.class, org.jumpserver.wisp.ServiceOuterClass.AgentRequest.Builder.class); + } + + public static final int ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object id_ = ""; + /** + * string id = 1; + * @return The id. + */ + @java.lang.Override + public java.lang.String getId() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } + } + /** + * string id = 1; + * @return The bytes for id. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int OPERATION_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object operation_ = ""; + /** + * string operation = 2; + * @return The operation. + */ + @java.lang.Override + public java.lang.String getOperation() { + java.lang.Object ref = operation_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + operation_ = s; + return s; + } + } + /** + * string operation = 2; + * @return The bytes for operation. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getOperationBytes() { + java.lang.Object ref = operation_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + operation_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int QUESTION_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object question_ = ""; + /** + * string question = 3; + * @return The question. + */ + @java.lang.Override + public java.lang.String getQuestion() { + java.lang.Object ref = question_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + question_ = s; + return s; + } + } + /** + * string question = 3; + * @return The bytes for question. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getQuestionBytes() { + java.lang.Object ref = question_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + question_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CONTEXT_JSON_FIELD_NUMBER = 4; + @SuppressWarnings("serial") + private volatile java.lang.Object contextJson_ = ""; + /** + * string context_json = 4; + * @return The contextJson. + */ + @java.lang.Override + public java.lang.String getContextJson() { + java.lang.Object ref = contextJson_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + contextJson_ = s; + return s; + } + } + /** + * string context_json = 4; + * @return The bytes for contextJson. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getContextJsonBytes() { + java.lang.Object ref = contextJson_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + contextJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(id_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(operation_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, operation_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(question_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, question_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(contextJson_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 4, contextJson_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(id_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(operation_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, operation_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(question_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, question_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(contextJson_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(4, contextJson_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentRequest)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentRequest other = (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) obj; + + if (!getId() + .equals(other.getId())) return false; + if (!getOperation() + .equals(other.getOperation())) return false; + if (!getQuestion() + .equals(other.getQuestion())) return false; + if (!getContextJson() + .equals(other.getContextJson())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId().hashCode(); + hash = (37 * hash) + OPERATION_FIELD_NUMBER; + hash = (53 * hash) + getOperation().hashCode(); + hash = (37 * hash) + QUESTION_FIELD_NUMBER; + hash = (53 * hash) + getQuestion().hashCode(); + hash = (37 * hash) + CONTEXT_JSON_FIELD_NUMBER; + hash = (53 * hash) + getContextJson().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentRequest prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentRequest} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentRequest) + org.jumpserver.wisp.ServiceOuterClass.AgentRequestOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentRequest_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentRequest.class, org.jumpserver.wisp.ServiceOuterClass.AgentRequest.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentRequest.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = ""; + operation_ = ""; + question_ = ""; + contextJson_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentRequest_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequest getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequest build() { + org.jumpserver.wisp.ServiceOuterClass.AgentRequest result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequest buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentRequest result = new org.jumpserver.wisp.ServiceOuterClass.AgentRequest(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentRequest result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.operation_ = operation_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.question_ = question_; + } + if (((from_bitField0_ & 0x00000008) != 0)) { + result.contextJson_ = contextJson_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentRequest) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentRequest)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentRequest other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance()) return this; + if (!other.getId().isEmpty()) { + id_ = other.id_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (!other.getOperation().isEmpty()) { + operation_ = other.operation_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (!other.getQuestion().isEmpty()) { + question_ = other.question_; + bitField0_ |= 0x00000004; + onChanged(); + } + if (!other.getContextJson().isEmpty()) { + contextJson_ = other.contextJson_; + bitField0_ |= 0x00000008; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + id_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + operation_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + question_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 26 + case 34: { + contextJson_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000008; + break; + } // case 34 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object id_ = ""; + /** + * string id = 1; + * @return The id. + */ + public java.lang.String getId() { + java.lang.Object ref = id_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string id = 1; + * @return The bytes for id. + */ + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string id = 1; + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string id = 1; + * @return This builder for chaining. + */ + public Builder clearId() { + id_ = getDefaultInstance().getId(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string id = 1; + * @param value The bytes for id to set. + * @return This builder for chaining. + */ + public Builder setIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private java.lang.Object operation_ = ""; + /** + * string operation = 2; + * @return The operation. + */ + public java.lang.String getOperation() { + java.lang.Object ref = operation_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + operation_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string operation = 2; + * @return The bytes for operation. + */ + public com.google.protobuf.ByteString + getOperationBytes() { + java.lang.Object ref = operation_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + operation_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string operation = 2; + * @param value The operation to set. + * @return This builder for chaining. + */ + public Builder setOperation( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + operation_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * string operation = 2; + * @return This builder for chaining. + */ + public Builder clearOperation() { + operation_ = getDefaultInstance().getOperation(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * string operation = 2; + * @param value The bytes for operation to set. + * @return This builder for chaining. + */ + public Builder setOperationBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + operation_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private java.lang.Object question_ = ""; + /** + * string question = 3; + * @return The question. + */ + public java.lang.String getQuestion() { + java.lang.Object ref = question_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + question_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string question = 3; + * @return The bytes for question. + */ + public com.google.protobuf.ByteString + getQuestionBytes() { + java.lang.Object ref = question_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + question_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string question = 3; + * @param value The question to set. + * @return This builder for chaining. + */ + public Builder setQuestion( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + question_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * string question = 3; + * @return This builder for chaining. + */ + public Builder clearQuestion() { + question_ = getDefaultInstance().getQuestion(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + /** + * string question = 3; + * @param value The bytes for question to set. + * @return This builder for chaining. + */ + public Builder setQuestionBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + question_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + private java.lang.Object contextJson_ = ""; + /** + * string context_json = 4; + * @return The contextJson. + */ + public java.lang.String getContextJson() { + java.lang.Object ref = contextJson_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + contextJson_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string context_json = 4; + * @return The bytes for contextJson. + */ + public com.google.protobuf.ByteString + getContextJsonBytes() { + java.lang.Object ref = contextJson_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + contextJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string context_json = 4; + * @param value The contextJson to set. + * @return This builder for chaining. + */ + public Builder setContextJson( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + contextJson_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + /** + * string context_json = 4; + * @return This builder for chaining. + */ + public Builder clearContextJson() { + contextJson_ = getDefaultInstance().getContextJson(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + /** + * string context_json = 4; + * @param value The bytes for contextJson to set. + * @return This builder for chaining. + */ + public Builder setContextJsonBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + contextJson_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentRequest) + } + + // @@protoc_insertion_point(class_scope:message.AgentRequest) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentRequest DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentRequest(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentRequest getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentRequest parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequest getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentToolResultOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentToolResult) + com.google.protobuf.MessageOrBuilder { + + /** + * string id = 1; + * @return The id. + */ + java.lang.String getId(); + /** + * string id = 1; + * @return The bytes for id. + */ + com.google.protobuf.ByteString + getIdBytes(); + + /** + * string result_json = 2; + * @return The resultJson. + */ + java.lang.String getResultJson(); + /** + * string result_json = 2; + * @return The bytes for resultJson. + */ + com.google.protobuf.ByteString + getResultJsonBytes(); + + /** + * string error = 3; + * @return The error. + */ + java.lang.String getError(); + /** + * string error = 3; + * @return The bytes for error. + */ + com.google.protobuf.ByteString + getErrorBytes(); + } + /** + * Protobuf type {@code message.AgentToolResult} + */ + public static final class AgentToolResult extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentToolResult) + AgentToolResultOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentToolResult.class.getName()); + } + // Use AgentToolResult.newBuilder() to construct. + private AgentToolResult(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentToolResult() { + id_ = ""; + resultJson_ = ""; + error_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolResult_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolResult_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.class, org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.Builder.class); + } + + public static final int ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object id_ = ""; + /** + * string id = 1; + * @return The id. + */ + @java.lang.Override + public java.lang.String getId() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } + } + /** + * string id = 1; + * @return The bytes for id. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int RESULT_JSON_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object resultJson_ = ""; + /** + * string result_json = 2; + * @return The resultJson. + */ + @java.lang.Override + public java.lang.String getResultJson() { + java.lang.Object ref = resultJson_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + resultJson_ = s; + return s; + } + } + /** + * string result_json = 2; + * @return The bytes for resultJson. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getResultJsonBytes() { + java.lang.Object ref = resultJson_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + resultJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ERROR_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object error_ = ""; + /** + * string error = 3; + * @return The error. + */ + @java.lang.Override + public java.lang.String getError() { + java.lang.Object ref = error_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + error_ = s; + return s; + } + } + /** + * string error = 3; + * @return The bytes for error. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getErrorBytes() { + java.lang.Object ref = error_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + error_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(id_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(resultJson_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, resultJson_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(error_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, error_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(id_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(resultJson_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, resultJson_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(error_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, error_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentToolResult)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult other = (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) obj; + + if (!getId() + .equals(other.getId())) return false; + if (!getResultJson() + .equals(other.getResultJson())) return false; + if (!getError() + .equals(other.getError())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId().hashCode(); + hash = (37 * hash) + RESULT_JSON_FIELD_NUMBER; + hash = (53 * hash) + getResultJson().hashCode(); + hash = (37 * hash) + ERROR_FIELD_NUMBER; + hash = (53 * hash) + getError().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentToolResult prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentToolResult} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentToolResult) + org.jumpserver.wisp.ServiceOuterClass.AgentToolResultOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolResult_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolResult_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.class, org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = ""; + resultJson_ = ""; + error_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolResult_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResult getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResult build() { + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResult buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult result = new org.jumpserver.wisp.ServiceOuterClass.AgentToolResult(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentToolResult result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.resultJson_ = resultJson_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.error_ = error_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentToolResult)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentToolResult other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance()) return this; + if (!other.getId().isEmpty()) { + id_ = other.id_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (!other.getResultJson().isEmpty()) { + resultJson_ = other.resultJson_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (!other.getError().isEmpty()) { + error_ = other.error_; + bitField0_ |= 0x00000004; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + id_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + resultJson_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + error_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 26 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object id_ = ""; + /** + * string id = 1; + * @return The id. + */ + public java.lang.String getId() { + java.lang.Object ref = id_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string id = 1; + * @return The bytes for id. + */ + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string id = 1; + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string id = 1; + * @return This builder for chaining. + */ + public Builder clearId() { + id_ = getDefaultInstance().getId(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string id = 1; + * @param value The bytes for id to set. + * @return This builder for chaining. + */ + public Builder setIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private java.lang.Object resultJson_ = ""; + /** + * string result_json = 2; + * @return The resultJson. + */ + public java.lang.String getResultJson() { + java.lang.Object ref = resultJson_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + resultJson_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string result_json = 2; + * @return The bytes for resultJson. + */ + public com.google.protobuf.ByteString + getResultJsonBytes() { + java.lang.Object ref = resultJson_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + resultJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string result_json = 2; + * @param value The resultJson to set. + * @return This builder for chaining. + */ + public Builder setResultJson( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + resultJson_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * string result_json = 2; + * @return This builder for chaining. + */ + public Builder clearResultJson() { + resultJson_ = getDefaultInstance().getResultJson(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * string result_json = 2; + * @param value The bytes for resultJson to set. + * @return This builder for chaining. + */ + public Builder setResultJsonBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + resultJson_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private java.lang.Object error_ = ""; + /** + * string error = 3; + * @return The error. + */ + public java.lang.String getError() { + java.lang.Object ref = error_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + error_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string error = 3; + * @return The bytes for error. + */ + public com.google.protobuf.ByteString + getErrorBytes() { + java.lang.Object ref = error_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + error_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string error = 3; + * @param value The error to set. + * @return This builder for chaining. + */ + public Builder setError( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + error_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * string error = 3; + * @return This builder for chaining. + */ + public Builder clearError() { + error_ = getDefaultInstance().getError(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + /** + * string error = 3; + * @param value The bytes for error to set. + * @return This builder for chaining. + */ + public Builder setErrorBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + error_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentToolResult) + } + + // @@protoc_insertion_point(class_scope:message.AgentToolResult) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentToolResult DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentToolResult(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolResult getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentToolResult parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResult getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentCancelOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentCancel) + com.google.protobuf.MessageOrBuilder { + + /** + * string request_id = 1; + * @return The requestId. + */ + java.lang.String getRequestId(); + /** + * string request_id = 1; + * @return The bytes for requestId. + */ + com.google.protobuf.ByteString + getRequestIdBytes(); + } + /** + * Protobuf type {@code message.AgentCancel} + */ + public static final class AgentCancel extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentCancel) + AgentCancelOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentCancel.class.getName()); + } + // Use AgentCancel.newBuilder() to construct. + private AgentCancel(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentCancel() { + requestId_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentCancel_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentCancel_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentCancel.class, org.jumpserver.wisp.ServiceOuterClass.AgentCancel.Builder.class); + } + + public static final int REQUEST_ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object requestId_ = ""; + /** + * string request_id = 1; + * @return The requestId. + */ + @java.lang.Override + public java.lang.String getRequestId() { + java.lang.Object ref = requestId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + requestId_ = s; + return s; + } + } + /** + * string request_id = 1; + * @return The bytes for requestId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getRequestIdBytes() { + java.lang.Object ref = requestId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + requestId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(requestId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, requestId_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(requestId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, requestId_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentCancel)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentCancel other = (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) obj; + + if (!getRequestId() + .equals(other.getRequestId())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + REQUEST_ID_FIELD_NUMBER; + hash = (53 * hash) + getRequestId().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentCancel prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentCancel} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentCancel) + org.jumpserver.wisp.ServiceOuterClass.AgentCancelOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentCancel_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentCancel_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentCancel.class, org.jumpserver.wisp.ServiceOuterClass.AgentCancel.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentCancel.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + requestId_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentCancel_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancel getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancel build() { + org.jumpserver.wisp.ServiceOuterClass.AgentCancel result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancel buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentCancel result = new org.jumpserver.wisp.ServiceOuterClass.AgentCancel(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentCancel result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.requestId_ = requestId_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentCancel) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentCancel)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentCancel other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance()) return this; + if (!other.getRequestId().isEmpty()) { + requestId_ = other.requestId_; + bitField0_ |= 0x00000001; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + requestId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object requestId_ = ""; + /** + * string request_id = 1; + * @return The requestId. + */ + public java.lang.String getRequestId() { + java.lang.Object ref = requestId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + requestId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string request_id = 1; + * @return The bytes for requestId. + */ + public com.google.protobuf.ByteString + getRequestIdBytes() { + java.lang.Object ref = requestId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + requestId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string request_id = 1; + * @param value The requestId to set. + * @return This builder for chaining. + */ + public Builder setRequestId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + requestId_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string request_id = 1; + * @return This builder for chaining. + */ + public Builder clearRequestId() { + requestId_ = getDefaultInstance().getRequestId(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string request_id = 1; + * @param value The bytes for requestId to set. + * @return This builder for chaining. + */ + public Builder setRequestIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + requestId_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentCancel) + } + + // @@protoc_insertion_point(class_scope:message.AgentCancel) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentCancel DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentCancel(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentCancel getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentCancel parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancel getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentClientEventOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentClientEvent) + com.google.protobuf.MessageOrBuilder { + + /** + * .message.AgentSessionOpen open = 1; + * @return Whether the open field is set. + */ + boolean hasOpen(); + /** + * .message.AgentSessionOpen open = 1; + * @return The open. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen getOpen(); + /** + * .message.AgentSessionOpen open = 1; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpenOrBuilder getOpenOrBuilder(); + + /** + * .message.AgentRequest request = 2; + * @return Whether the request field is set. + */ + boolean hasRequest(); + /** + * .message.AgentRequest request = 2; + * @return The request. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentRequest getRequest(); + /** + * .message.AgentRequest request = 2; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentRequestOrBuilder getRequestOrBuilder(); + + /** + * .message.AgentToolResult tool_result = 3; + * @return Whether the toolResult field is set. + */ + boolean hasToolResult(); + /** + * .message.AgentToolResult tool_result = 3; + * @return The toolResult. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult getToolResult(); + /** + * .message.AgentToolResult tool_result = 3; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentToolResultOrBuilder getToolResultOrBuilder(); + + /** + * .message.AgentCancel cancel = 4; + * @return Whether the cancel field is set. + */ + boolean hasCancel(); + /** + * .message.AgentCancel cancel = 4; + * @return The cancel. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentCancel getCancel(); + /** + * .message.AgentCancel cancel = 4; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentCancelOrBuilder getCancelOrBuilder(); + + /** + * .message.Empty close = 5; + * @return Whether the close field is set. + */ + boolean hasClose(); + /** + * .message.Empty close = 5; + * @return The close. + */ + org.jumpserver.wisp.ServiceOuterClass.Empty getClose(); + /** + * .message.Empty close = 5; + */ + org.jumpserver.wisp.ServiceOuterClass.EmptyOrBuilder getCloseOrBuilder(); + + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.EventCase getEventCase(); + } + /** + * Protobuf type {@code message.AgentClientEvent} + */ + public static final class AgentClientEvent extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentClientEvent) + AgentClientEventOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentClientEvent.class.getName()); + } + // Use AgentClientEvent.newBuilder() to construct. + private AgentClientEvent(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentClientEvent() { + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentClientEvent_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentClientEvent_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.class, org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.Builder.class); + } + + private int eventCase_ = 0; + @SuppressWarnings("serial") + private java.lang.Object event_; + public enum EventCase + implements com.google.protobuf.Internal.EnumLite, + com.google.protobuf.AbstractMessage.InternalOneOfEnum { + OPEN(1), + REQUEST(2), + TOOL_RESULT(3), + CANCEL(4), + CLOSE(5), + EVENT_NOT_SET(0); + private final int value; + private EventCase(int value) { + this.value = value; + } + /** + * @param value The number of the enum to look for. + * @return The enum associated with the given number. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static EventCase valueOf(int value) { + return forNumber(value); + } + + public static EventCase forNumber(int value) { + switch (value) { + case 1: return OPEN; + case 2: return REQUEST; + case 3: return TOOL_RESULT; + case 4: return CANCEL; + case 5: return CLOSE; + case 0: return EVENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public EventCase + getEventCase() { + return EventCase.forNumber( + eventCase_); + } + + public static final int OPEN_FIELD_NUMBER = 1; + /** + * .message.AgentSessionOpen open = 1; + * @return Whether the open field is set. + */ + @java.lang.Override + public boolean hasOpen() { + return eventCase_ == 1; + } + /** + * .message.AgentSessionOpen open = 1; + * @return The open. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen getOpen() { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance(); + } + /** + * .message.AgentSessionOpen open = 1; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpenOrBuilder getOpenOrBuilder() { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance(); + } + + public static final int REQUEST_FIELD_NUMBER = 2; + /** + * .message.AgentRequest request = 2; + * @return Whether the request field is set. + */ + @java.lang.Override + public boolean hasRequest() { + return eventCase_ == 2; + } + /** + * .message.AgentRequest request = 2; + * @return The request. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequest getRequest() { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance(); + } + /** + * .message.AgentRequest request = 2; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequestOrBuilder getRequestOrBuilder() { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance(); + } + + public static final int TOOL_RESULT_FIELD_NUMBER = 3; + /** + * .message.AgentToolResult tool_result = 3; + * @return Whether the toolResult field is set. + */ + @java.lang.Override + public boolean hasToolResult() { + return eventCase_ == 3; + } + /** + * .message.AgentToolResult tool_result = 3; + * @return The toolResult. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResult getToolResult() { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance(); + } + /** + * .message.AgentToolResult tool_result = 3; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResultOrBuilder getToolResultOrBuilder() { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance(); + } + + public static final int CANCEL_FIELD_NUMBER = 4; + /** + * .message.AgentCancel cancel = 4; + * @return Whether the cancel field is set. + */ + @java.lang.Override + public boolean hasCancel() { + return eventCase_ == 4; + } + /** + * .message.AgentCancel cancel = 4; + * @return The cancel. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancel getCancel() { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance(); + } + /** + * .message.AgentCancel cancel = 4; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancelOrBuilder getCancelOrBuilder() { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance(); + } + + public static final int CLOSE_FIELD_NUMBER = 5; + /** + * .message.Empty close = 5; + * @return Whether the close field is set. + */ + @java.lang.Override + public boolean hasClose() { + return eventCase_ == 5; + } + /** + * .message.Empty close = 5; + * @return The close. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.Empty getClose() { + if (eventCase_ == 5) { + return (org.jumpserver.wisp.ServiceOuterClass.Empty) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.Empty.getDefaultInstance(); + } + /** + * .message.Empty close = 5; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.EmptyOrBuilder getCloseOrBuilder() { + if (eventCase_ == 5) { + return (org.jumpserver.wisp.ServiceOuterClass.Empty) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.Empty.getDefaultInstance(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (eventCase_ == 1) { + output.writeMessage(1, (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_); + } + if (eventCase_ == 2) { + output.writeMessage(2, (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_); + } + if (eventCase_ == 3) { + output.writeMessage(3, (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_); + } + if (eventCase_ == 4) { + output.writeMessage(4, (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_); + } + if (eventCase_ == 5) { + output.writeMessage(5, (org.jumpserver.wisp.ServiceOuterClass.Empty) event_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (eventCase_ == 1) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_); + } + if (eventCase_ == 2) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_); + } + if (eventCase_ == 3) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_); + } + if (eventCase_ == 4) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(4, (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_); + } + if (eventCase_ == 5) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(5, (org.jumpserver.wisp.ServiceOuterClass.Empty) event_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent other = (org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent) obj; + + if (!getEventCase().equals(other.getEventCase())) return false; + switch (eventCase_) { + case 1: + if (!getOpen() + .equals(other.getOpen())) return false; + break; + case 2: + if (!getRequest() + .equals(other.getRequest())) return false; + break; + case 3: + if (!getToolResult() + .equals(other.getToolResult())) return false; + break; + case 4: + if (!getCancel() + .equals(other.getCancel())) return false; + break; + case 5: + if (!getClose() + .equals(other.getClose())) return false; + break; + case 0: + default: + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + switch (eventCase_) { + case 1: + hash = (37 * hash) + OPEN_FIELD_NUMBER; + hash = (53 * hash) + getOpen().hashCode(); + break; + case 2: + hash = (37 * hash) + REQUEST_FIELD_NUMBER; + hash = (53 * hash) + getRequest().hashCode(); + break; + case 3: + hash = (37 * hash) + TOOL_RESULT_FIELD_NUMBER; + hash = (53 * hash) + getToolResult().hashCode(); + break; + case 4: + hash = (37 * hash) + CANCEL_FIELD_NUMBER; + hash = (53 * hash) + getCancel().hashCode(); + break; + case 5: + hash = (37 * hash) + CLOSE_FIELD_NUMBER; + hash = (53 * hash) + getClose().hashCode(); + break; + case 0: + default: + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentClientEvent} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentClientEvent) + org.jumpserver.wisp.ServiceOuterClass.AgentClientEventOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentClientEvent_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentClientEvent_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.class, org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (openBuilder_ != null) { + openBuilder_.clear(); + } + if (requestBuilder_ != null) { + requestBuilder_.clear(); + } + if (toolResultBuilder_ != null) { + toolResultBuilder_.clear(); + } + if (cancelBuilder_ != null) { + cancelBuilder_.clear(); + } + if (closeBuilder_ != null) { + closeBuilder_.clear(); + } + eventCase_ = 0; + event_ = null; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentClientEvent_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent build() { + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent result = new org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent(this); + if (bitField0_ != 0) { buildPartial0(result); } + buildPartialOneofs(result); + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent result) { + int from_bitField0_ = bitField0_; + } + + private void buildPartialOneofs(org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent result) { + result.eventCase_ = eventCase_; + result.event_ = this.event_; + if (eventCase_ == 1 && + openBuilder_ != null) { + result.event_ = openBuilder_.build(); + } + if (eventCase_ == 2 && + requestBuilder_ != null) { + result.event_ = requestBuilder_.build(); + } + if (eventCase_ == 3 && + toolResultBuilder_ != null) { + result.event_ = toolResultBuilder_.build(); + } + if (eventCase_ == 4 && + cancelBuilder_ != null) { + result.event_ = cancelBuilder_.build(); + } + if (eventCase_ == 5 && + closeBuilder_ != null) { + result.event_ = closeBuilder_.build(); + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent.getDefaultInstance()) return this; + switch (other.getEventCase()) { + case OPEN: { + mergeOpen(other.getOpen()); + break; + } + case REQUEST: { + mergeRequest(other.getRequest()); + break; + } + case TOOL_RESULT: { + mergeToolResult(other.getToolResult()); + break; + } + case CANCEL: { + mergeCancel(other.getCancel()); + break; + } + case CLOSE: { + mergeClose(other.getClose()); + break; + } + case EVENT_NOT_SET: { + break; + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + input.readMessage( + internalGetOpenFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 1; + break; + } // case 10 + case 18: { + input.readMessage( + internalGetRequestFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 2; + break; + } // case 18 + case 26: { + input.readMessage( + internalGetToolResultFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 3; + break; + } // case 26 + case 34: { + input.readMessage( + internalGetCancelFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 4; + break; + } // case 34 + case 42: { + input.readMessage( + internalGetCloseFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 5; + break; + } // case 42 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int eventCase_ = 0; + private java.lang.Object event_; + public EventCase + getEventCase() { + return EventCase.forNumber( + eventCase_); + } + + public Builder clearEvent() { + eventCase_ = 0; + event_ = null; + onChanged(); + return this; + } + + private int bitField0_; + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpenOrBuilder> openBuilder_; + /** + * .message.AgentSessionOpen open = 1; + * @return Whether the open field is set. + */ + @java.lang.Override + public boolean hasOpen() { + return eventCase_ == 1; + } + /** + * .message.AgentSessionOpen open = 1; + * @return The open. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen getOpen() { + if (openBuilder_ == null) { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance(); + } else { + if (eventCase_ == 1) { + return openBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance(); + } + } + /** + * .message.AgentSessionOpen open = 1; + */ + public Builder setOpen(org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen value) { + if (openBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + openBuilder_.setMessage(value); + } + eventCase_ = 1; + return this; + } + /** + * .message.AgentSessionOpen open = 1; + */ + public Builder setOpen( + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.Builder builderForValue) { + if (openBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + openBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 1; + return this; + } + /** + * .message.AgentSessionOpen open = 1; + */ + public Builder mergeOpen(org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen value) { + if (openBuilder_ == null) { + if (eventCase_ == 1 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 1) { + openBuilder_.mergeFrom(value); + } else { + openBuilder_.setMessage(value); + } + } + eventCase_ = 1; + return this; + } + /** + * .message.AgentSessionOpen open = 1; + */ + public Builder clearOpen() { + if (openBuilder_ == null) { + if (eventCase_ == 1) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 1) { + eventCase_ = 0; + event_ = null; + } + openBuilder_.clear(); + } + return this; + } + /** + * .message.AgentSessionOpen open = 1; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.Builder getOpenBuilder() { + return internalGetOpenFieldBuilder().getBuilder(); + } + /** + * .message.AgentSessionOpen open = 1; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpenOrBuilder getOpenOrBuilder() { + if ((eventCase_ == 1) && (openBuilder_ != null)) { + return openBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance(); + } + } + /** + * .message.AgentSessionOpen open = 1; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpenOrBuilder> + internalGetOpenFieldBuilder() { + if (openBuilder_ == null) { + if (!(eventCase_ == 1)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.getDefaultInstance(); + } + openBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpenOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentSessionOpen) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 1; + onChanged(); + return openBuilder_; + } + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentRequest, org.jumpserver.wisp.ServiceOuterClass.AgentRequest.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentRequestOrBuilder> requestBuilder_; + /** + * .message.AgentRequest request = 2; + * @return Whether the request field is set. + */ + @java.lang.Override + public boolean hasRequest() { + return eventCase_ == 2; + } + /** + * .message.AgentRequest request = 2; + * @return The request. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequest getRequest() { + if (requestBuilder_ == null) { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance(); + } else { + if (eventCase_ == 2) { + return requestBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance(); + } + } + /** + * .message.AgentRequest request = 2; + */ + public Builder setRequest(org.jumpserver.wisp.ServiceOuterClass.AgentRequest value) { + if (requestBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + requestBuilder_.setMessage(value); + } + eventCase_ = 2; + return this; + } + /** + * .message.AgentRequest request = 2; + */ + public Builder setRequest( + org.jumpserver.wisp.ServiceOuterClass.AgentRequest.Builder builderForValue) { + if (requestBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + requestBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 2; + return this; + } + /** + * .message.AgentRequest request = 2; + */ + public Builder mergeRequest(org.jumpserver.wisp.ServiceOuterClass.AgentRequest value) { + if (requestBuilder_ == null) { + if (eventCase_ == 2 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentRequest.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 2) { + requestBuilder_.mergeFrom(value); + } else { + requestBuilder_.setMessage(value); + } + } + eventCase_ = 2; + return this; + } + /** + * .message.AgentRequest request = 2; + */ + public Builder clearRequest() { + if (requestBuilder_ == null) { + if (eventCase_ == 2) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 2) { + eventCase_ = 0; + event_ = null; + } + requestBuilder_.clear(); + } + return this; + } + /** + * .message.AgentRequest request = 2; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentRequest.Builder getRequestBuilder() { + return internalGetRequestFieldBuilder().getBuilder(); + } + /** + * .message.AgentRequest request = 2; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentRequestOrBuilder getRequestOrBuilder() { + if ((eventCase_ == 2) && (requestBuilder_ != null)) { + return requestBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance(); + } + } + /** + * .message.AgentRequest request = 2; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentRequest, org.jumpserver.wisp.ServiceOuterClass.AgentRequest.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentRequestOrBuilder> + internalGetRequestFieldBuilder() { + if (requestBuilder_ == null) { + if (!(eventCase_ == 2)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentRequest.getDefaultInstance(); + } + requestBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentRequest, org.jumpserver.wisp.ServiceOuterClass.AgentRequest.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentRequestOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentRequest) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 2; + onChanged(); + return requestBuilder_; + } + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult, org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentToolResultOrBuilder> toolResultBuilder_; + /** + * .message.AgentToolResult tool_result = 3; + * @return Whether the toolResult field is set. + */ + @java.lang.Override + public boolean hasToolResult() { + return eventCase_ == 3; + } + /** + * .message.AgentToolResult tool_result = 3; + * @return The toolResult. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResult getToolResult() { + if (toolResultBuilder_ == null) { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance(); + } else { + if (eventCase_ == 3) { + return toolResultBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance(); + } + } + /** + * .message.AgentToolResult tool_result = 3; + */ + public Builder setToolResult(org.jumpserver.wisp.ServiceOuterClass.AgentToolResult value) { + if (toolResultBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + toolResultBuilder_.setMessage(value); + } + eventCase_ = 3; + return this; + } + /** + * .message.AgentToolResult tool_result = 3; + */ + public Builder setToolResult( + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.Builder builderForValue) { + if (toolResultBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + toolResultBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 3; + return this; + } + /** + * .message.AgentToolResult tool_result = 3; + */ + public Builder mergeToolResult(org.jumpserver.wisp.ServiceOuterClass.AgentToolResult value) { + if (toolResultBuilder_ == null) { + if (eventCase_ == 3 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 3) { + toolResultBuilder_.mergeFrom(value); + } else { + toolResultBuilder_.setMessage(value); + } + } + eventCase_ = 3; + return this; + } + /** + * .message.AgentToolResult tool_result = 3; + */ + public Builder clearToolResult() { + if (toolResultBuilder_ == null) { + if (eventCase_ == 3) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 3) { + eventCase_ = 0; + event_ = null; + } + toolResultBuilder_.clear(); + } + return this; + } + /** + * .message.AgentToolResult tool_result = 3; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.Builder getToolResultBuilder() { + return internalGetToolResultFieldBuilder().getBuilder(); + } + /** + * .message.AgentToolResult tool_result = 3; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolResultOrBuilder getToolResultOrBuilder() { + if ((eventCase_ == 3) && (toolResultBuilder_ != null)) { + return toolResultBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance(); + } + } + /** + * .message.AgentToolResult tool_result = 3; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult, org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentToolResultOrBuilder> + internalGetToolResultFieldBuilder() { + if (toolResultBuilder_ == null) { + if (!(eventCase_ == 3)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.getDefaultInstance(); + } + toolResultBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentToolResult, org.jumpserver.wisp.ServiceOuterClass.AgentToolResult.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentToolResultOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentToolResult) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 3; + onChanged(); + return toolResultBuilder_; + } + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentCancel, org.jumpserver.wisp.ServiceOuterClass.AgentCancel.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentCancelOrBuilder> cancelBuilder_; + /** + * .message.AgentCancel cancel = 4; + * @return Whether the cancel field is set. + */ + @java.lang.Override + public boolean hasCancel() { + return eventCase_ == 4; + } + /** + * .message.AgentCancel cancel = 4; + * @return The cancel. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancel getCancel() { + if (cancelBuilder_ == null) { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance(); + } else { + if (eventCase_ == 4) { + return cancelBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance(); + } + } + /** + * .message.AgentCancel cancel = 4; + */ + public Builder setCancel(org.jumpserver.wisp.ServiceOuterClass.AgentCancel value) { + if (cancelBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + cancelBuilder_.setMessage(value); + } + eventCase_ = 4; + return this; + } + /** + * .message.AgentCancel cancel = 4; + */ + public Builder setCancel( + org.jumpserver.wisp.ServiceOuterClass.AgentCancel.Builder builderForValue) { + if (cancelBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + cancelBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 4; + return this; + } + /** + * .message.AgentCancel cancel = 4; + */ + public Builder mergeCancel(org.jumpserver.wisp.ServiceOuterClass.AgentCancel value) { + if (cancelBuilder_ == null) { + if (eventCase_ == 4 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentCancel.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 4) { + cancelBuilder_.mergeFrom(value); + } else { + cancelBuilder_.setMessage(value); + } + } + eventCase_ = 4; + return this; + } + /** + * .message.AgentCancel cancel = 4; + */ + public Builder clearCancel() { + if (cancelBuilder_ == null) { + if (eventCase_ == 4) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 4) { + eventCase_ = 0; + event_ = null; + } + cancelBuilder_.clear(); + } + return this; + } + /** + * .message.AgentCancel cancel = 4; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentCancel.Builder getCancelBuilder() { + return internalGetCancelFieldBuilder().getBuilder(); + } + /** + * .message.AgentCancel cancel = 4; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentCancelOrBuilder getCancelOrBuilder() { + if ((eventCase_ == 4) && (cancelBuilder_ != null)) { + return cancelBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance(); + } + } + /** + * .message.AgentCancel cancel = 4; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentCancel, org.jumpserver.wisp.ServiceOuterClass.AgentCancel.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentCancelOrBuilder> + internalGetCancelFieldBuilder() { + if (cancelBuilder_ == null) { + if (!(eventCase_ == 4)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentCancel.getDefaultInstance(); + } + cancelBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentCancel, org.jumpserver.wisp.ServiceOuterClass.AgentCancel.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentCancelOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentCancel) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 4; + onChanged(); + return cancelBuilder_; + } + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.Empty, org.jumpserver.wisp.ServiceOuterClass.Empty.Builder, org.jumpserver.wisp.ServiceOuterClass.EmptyOrBuilder> closeBuilder_; + /** + * .message.Empty close = 5; + * @return Whether the close field is set. + */ + @java.lang.Override + public boolean hasClose() { + return eventCase_ == 5; + } + /** + * .message.Empty close = 5; + * @return The close. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.Empty getClose() { + if (closeBuilder_ == null) { + if (eventCase_ == 5) { + return (org.jumpserver.wisp.ServiceOuterClass.Empty) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.Empty.getDefaultInstance(); + } else { + if (eventCase_ == 5) { + return closeBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.Empty.getDefaultInstance(); + } + } + /** + * .message.Empty close = 5; + */ + public Builder setClose(org.jumpserver.wisp.ServiceOuterClass.Empty value) { + if (closeBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + closeBuilder_.setMessage(value); + } + eventCase_ = 5; + return this; + } + /** + * .message.Empty close = 5; + */ + public Builder setClose( + org.jumpserver.wisp.ServiceOuterClass.Empty.Builder builderForValue) { + if (closeBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + closeBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 5; + return this; + } + /** + * .message.Empty close = 5; + */ + public Builder mergeClose(org.jumpserver.wisp.ServiceOuterClass.Empty value) { + if (closeBuilder_ == null) { + if (eventCase_ == 5 && + event_ != org.jumpserver.wisp.ServiceOuterClass.Empty.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.Empty.newBuilder((org.jumpserver.wisp.ServiceOuterClass.Empty) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 5) { + closeBuilder_.mergeFrom(value); + } else { + closeBuilder_.setMessage(value); + } + } + eventCase_ = 5; + return this; + } + /** + * .message.Empty close = 5; + */ + public Builder clearClose() { + if (closeBuilder_ == null) { + if (eventCase_ == 5) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 5) { + eventCase_ = 0; + event_ = null; + } + closeBuilder_.clear(); + } + return this; + } + /** + * .message.Empty close = 5; + */ + public org.jumpserver.wisp.ServiceOuterClass.Empty.Builder getCloseBuilder() { + return internalGetCloseFieldBuilder().getBuilder(); + } + /** + * .message.Empty close = 5; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.EmptyOrBuilder getCloseOrBuilder() { + if ((eventCase_ == 5) && (closeBuilder_ != null)) { + return closeBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 5) { + return (org.jumpserver.wisp.ServiceOuterClass.Empty) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.Empty.getDefaultInstance(); + } + } + /** + * .message.Empty close = 5; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.Empty, org.jumpserver.wisp.ServiceOuterClass.Empty.Builder, org.jumpserver.wisp.ServiceOuterClass.EmptyOrBuilder> + internalGetCloseFieldBuilder() { + if (closeBuilder_ == null) { + if (!(eventCase_ == 5)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.Empty.getDefaultInstance(); + } + closeBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.Empty, org.jumpserver.wisp.ServiceOuterClass.Empty.Builder, org.jumpserver.wisp.ServiceOuterClass.EmptyOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.Empty) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 5; + onChanged(); + return closeBuilder_; + } + + // @@protoc_insertion_point(builder_scope:message.AgentClientEvent) + } + + // @@protoc_insertion_point(class_scope:message.AgentClientEvent) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentClientEvent parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentClientEvent getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentReadyOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentReady) + com.google.protobuf.MessageOrBuilder { + + /** + * bool enabled = 1; + * @return The enabled. + */ + boolean getEnabled(); + + /** + * string reason = 2; + * @return The reason. + */ + java.lang.String getReason(); + /** + * string reason = 2; + * @return The bytes for reason. + */ + com.google.protobuf.ByteString + getReasonBytes(); + + /** + * string session_id = 3; + * @return The sessionId. + */ + java.lang.String getSessionId(); + /** + * string session_id = 3; + * @return The bytes for sessionId. + */ + com.google.protobuf.ByteString + getSessionIdBytes(); + + /** + * string surface = 4; + * @return The surface. + */ + java.lang.String getSurface(); + /** + * string surface = 4; + * @return The bytes for surface. + */ + com.google.protobuf.ByteString + getSurfaceBytes(); + + /** + * string provider = 5; + * @return The provider. + */ + java.lang.String getProvider(); + /** + * string provider = 5; + * @return The bytes for provider. + */ + com.google.protobuf.ByteString + getProviderBytes(); + + /** + * string model = 6; + * @return The model. + */ + java.lang.String getModel(); + /** + * string model = 6; + * @return The bytes for model. + */ + com.google.protobuf.ByteString + getModelBytes(); + } + /** + * Protobuf type {@code message.AgentReady} + */ + public static final class AgentReady extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentReady) + AgentReadyOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentReady.class.getName()); + } + // Use AgentReady.newBuilder() to construct. + private AgentReady(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentReady() { + reason_ = ""; + sessionId_ = ""; + surface_ = ""; + provider_ = ""; + model_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentReady_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentReady_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentReady.class, org.jumpserver.wisp.ServiceOuterClass.AgentReady.Builder.class); + } + + public static final int ENABLED_FIELD_NUMBER = 1; + private boolean enabled_ = false; + /** + * bool enabled = 1; + * @return The enabled. + */ + @java.lang.Override + public boolean getEnabled() { + return enabled_; + } + + public static final int REASON_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object reason_ = ""; + /** + * string reason = 2; + * @return The reason. + */ + @java.lang.Override + public java.lang.String getReason() { + java.lang.Object ref = reason_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + reason_ = s; + return s; + } + } + /** + * string reason = 2; + * @return The bytes for reason. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getReasonBytes() { + java.lang.Object ref = reason_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + reason_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int SESSION_ID_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object sessionId_ = ""; + /** + * string session_id = 3; + * @return The sessionId. + */ + @java.lang.Override + public java.lang.String getSessionId() { + java.lang.Object ref = sessionId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + sessionId_ = s; + return s; + } + } + /** + * string session_id = 3; + * @return The bytes for sessionId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getSessionIdBytes() { + java.lang.Object ref = sessionId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sessionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int SURFACE_FIELD_NUMBER = 4; + @SuppressWarnings("serial") + private volatile java.lang.Object surface_ = ""; + /** + * string surface = 4; + * @return The surface. + */ + @java.lang.Override + public java.lang.String getSurface() { + java.lang.Object ref = surface_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + surface_ = s; + return s; + } + } + /** + * string surface = 4; + * @return The bytes for surface. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getSurfaceBytes() { + java.lang.Object ref = surface_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + surface_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PROVIDER_FIELD_NUMBER = 5; + @SuppressWarnings("serial") + private volatile java.lang.Object provider_ = ""; + /** + * string provider = 5; + * @return The provider. + */ + @java.lang.Override + public java.lang.String getProvider() { + java.lang.Object ref = provider_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + provider_ = s; + return s; + } + } + /** + * string provider = 5; + * @return The bytes for provider. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getProviderBytes() { + java.lang.Object ref = provider_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + provider_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int MODEL_FIELD_NUMBER = 6; + @SuppressWarnings("serial") + private volatile java.lang.Object model_ = ""; + /** + * string model = 6; + * @return The model. + */ + @java.lang.Override + public java.lang.String getModel() { + java.lang.Object ref = model_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + model_ = s; + return s; + } + } + /** + * string model = 6; + * @return The bytes for model. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getModelBytes() { + java.lang.Object ref = model_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + model_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (enabled_ != false) { + output.writeBool(1, enabled_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(reason_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, reason_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(sessionId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, sessionId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(surface_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 4, surface_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(provider_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 5, provider_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(model_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 6, model_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (enabled_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(1, enabled_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(reason_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, reason_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(sessionId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, sessionId_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(surface_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(4, surface_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(provider_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(5, provider_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(model_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(6, model_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentReady)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentReady other = (org.jumpserver.wisp.ServiceOuterClass.AgentReady) obj; + + if (getEnabled() + != other.getEnabled()) return false; + if (!getReason() + .equals(other.getReason())) return false; + if (!getSessionId() + .equals(other.getSessionId())) return false; + if (!getSurface() + .equals(other.getSurface())) return false; + if (!getProvider() + .equals(other.getProvider())) return false; + if (!getModel() + .equals(other.getModel())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ENABLED_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getEnabled()); + hash = (37 * hash) + REASON_FIELD_NUMBER; + hash = (53 * hash) + getReason().hashCode(); + hash = (37 * hash) + SESSION_ID_FIELD_NUMBER; + hash = (53 * hash) + getSessionId().hashCode(); + hash = (37 * hash) + SURFACE_FIELD_NUMBER; + hash = (53 * hash) + getSurface().hashCode(); + hash = (37 * hash) + PROVIDER_FIELD_NUMBER; + hash = (53 * hash) + getProvider().hashCode(); + hash = (37 * hash) + MODEL_FIELD_NUMBER; + hash = (53 * hash) + getModel().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentReady prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentReady} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentReady) + org.jumpserver.wisp.ServiceOuterClass.AgentReadyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentReady_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentReady_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentReady.class, org.jumpserver.wisp.ServiceOuterClass.AgentReady.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentReady.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + enabled_ = false; + reason_ = ""; + sessionId_ = ""; + surface_ = ""; + provider_ = ""; + model_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentReady_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReady getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReady build() { + org.jumpserver.wisp.ServiceOuterClass.AgentReady result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReady buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentReady result = new org.jumpserver.wisp.ServiceOuterClass.AgentReady(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentReady result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.enabled_ = enabled_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.reason_ = reason_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.sessionId_ = sessionId_; + } + if (((from_bitField0_ & 0x00000008) != 0)) { + result.surface_ = surface_; + } + if (((from_bitField0_ & 0x00000010) != 0)) { + result.provider_ = provider_; + } + if (((from_bitField0_ & 0x00000020) != 0)) { + result.model_ = model_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentReady) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentReady)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentReady other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance()) return this; + if (other.getEnabled() != false) { + setEnabled(other.getEnabled()); + } + if (!other.getReason().isEmpty()) { + reason_ = other.reason_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (!other.getSessionId().isEmpty()) { + sessionId_ = other.sessionId_; + bitField0_ |= 0x00000004; + onChanged(); + } + if (!other.getSurface().isEmpty()) { + surface_ = other.surface_; + bitField0_ |= 0x00000008; + onChanged(); + } + if (!other.getProvider().isEmpty()) { + provider_ = other.provider_; + bitField0_ |= 0x00000010; + onChanged(); + } + if (!other.getModel().isEmpty()) { + model_ = other.model_; + bitField0_ |= 0x00000020; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + enabled_ = input.readBool(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 18: { + reason_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + sessionId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 26 + case 34: { + surface_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000008; + break; + } // case 34 + case 42: { + provider_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000010; + break; + } // case 42 + case 50: { + model_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000020; + break; + } // case 50 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private boolean enabled_ ; + /** + * bool enabled = 1; + * @return The enabled. + */ + @java.lang.Override + public boolean getEnabled() { + return enabled_; + } + /** + * bool enabled = 1; + * @param value The enabled to set. + * @return This builder for chaining. + */ + public Builder setEnabled(boolean value) { + + enabled_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * bool enabled = 1; + * @return This builder for chaining. + */ + public Builder clearEnabled() { + bitField0_ = (bitField0_ & ~0x00000001); + enabled_ = false; + onChanged(); + return this; + } + + private java.lang.Object reason_ = ""; + /** + * string reason = 2; + * @return The reason. + */ + public java.lang.String getReason() { + java.lang.Object ref = reason_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + reason_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string reason = 2; + * @return The bytes for reason. + */ + public com.google.protobuf.ByteString + getReasonBytes() { + java.lang.Object ref = reason_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + reason_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string reason = 2; + * @param value The reason to set. + * @return This builder for chaining. + */ + public Builder setReason( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + reason_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * string reason = 2; + * @return This builder for chaining. + */ + public Builder clearReason() { + reason_ = getDefaultInstance().getReason(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * string reason = 2; + * @param value The bytes for reason to set. + * @return This builder for chaining. + */ + public Builder setReasonBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + reason_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private java.lang.Object sessionId_ = ""; + /** + * string session_id = 3; + * @return The sessionId. + */ + public java.lang.String getSessionId() { + java.lang.Object ref = sessionId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + sessionId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string session_id = 3; + * @return The bytes for sessionId. + */ + public com.google.protobuf.ByteString + getSessionIdBytes() { + java.lang.Object ref = sessionId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + sessionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string session_id = 3; + * @param value The sessionId to set. + * @return This builder for chaining. + */ + public Builder setSessionId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + sessionId_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * string session_id = 3; + * @return This builder for chaining. + */ + public Builder clearSessionId() { + sessionId_ = getDefaultInstance().getSessionId(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + /** + * string session_id = 3; + * @param value The bytes for sessionId to set. + * @return This builder for chaining. + */ + public Builder setSessionIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + sessionId_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + private java.lang.Object surface_ = ""; + /** + * string surface = 4; + * @return The surface. + */ + public java.lang.String getSurface() { + java.lang.Object ref = surface_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + surface_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string surface = 4; + * @return The bytes for surface. + */ + public com.google.protobuf.ByteString + getSurfaceBytes() { + java.lang.Object ref = surface_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + surface_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string surface = 4; + * @param value The surface to set. + * @return This builder for chaining. + */ + public Builder setSurface( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + surface_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + /** + * string surface = 4; + * @return This builder for chaining. + */ + public Builder clearSurface() { + surface_ = getDefaultInstance().getSurface(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + /** + * string surface = 4; + * @param value The bytes for surface to set. + * @return This builder for chaining. + */ + public Builder setSurfaceBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + surface_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + private java.lang.Object provider_ = ""; + /** + * string provider = 5; + * @return The provider. + */ + public java.lang.String getProvider() { + java.lang.Object ref = provider_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + provider_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string provider = 5; + * @return The bytes for provider. + */ + public com.google.protobuf.ByteString + getProviderBytes() { + java.lang.Object ref = provider_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + provider_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string provider = 5; + * @param value The provider to set. + * @return This builder for chaining. + */ + public Builder setProvider( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + provider_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; + } + /** + * string provider = 5; + * @return This builder for chaining. + */ + public Builder clearProvider() { + provider_ = getDefaultInstance().getProvider(); + bitField0_ = (bitField0_ & ~0x00000010); + onChanged(); + return this; + } + /** + * string provider = 5; + * @param value The bytes for provider to set. + * @return This builder for chaining. + */ + public Builder setProviderBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + provider_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; + } + + private java.lang.Object model_ = ""; + /** + * string model = 6; + * @return The model. + */ + public java.lang.String getModel() { + java.lang.Object ref = model_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + model_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string model = 6; + * @return The bytes for model. + */ + public com.google.protobuf.ByteString + getModelBytes() { + java.lang.Object ref = model_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + model_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string model = 6; + * @param value The model to set. + * @return This builder for chaining. + */ + public Builder setModel( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + model_ = value; + bitField0_ |= 0x00000020; + onChanged(); + return this; + } + /** + * string model = 6; + * @return This builder for chaining. + */ + public Builder clearModel() { + model_ = getDefaultInstance().getModel(); + bitField0_ = (bitField0_ & ~0x00000020); + onChanged(); + return this; + } + /** + * string model = 6; + * @param value The bytes for model to set. + * @return This builder for chaining. + */ + public Builder setModelBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + model_ = value; + bitField0_ |= 0x00000020; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentReady) + } + + // @@protoc_insertion_point(class_scope:message.AgentReady) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentReady DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentReady(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentReady getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentReady parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReady getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentChatMessageOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentChatMessage) + com.google.protobuf.MessageOrBuilder { + + /** + * string message_json = 1; + * @return The messageJson. + */ + java.lang.String getMessageJson(); + /** + * string message_json = 1; + * @return The bytes for messageJson. + */ + com.google.protobuf.ByteString + getMessageJsonBytes(); + } + /** + * Protobuf type {@code message.AgentChatMessage} + */ + public static final class AgentChatMessage extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentChatMessage) + AgentChatMessageOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentChatMessage.class.getName()); + } + // Use AgentChatMessage.newBuilder() to construct. + private AgentChatMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentChatMessage() { + messageJson_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentChatMessage_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentChatMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.class, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.Builder.class); + } + + public static final int MESSAGE_JSON_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object messageJson_ = ""; + /** + * string message_json = 1; + * @return The messageJson. + */ + @java.lang.Override + public java.lang.String getMessageJson() { + java.lang.Object ref = messageJson_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + messageJson_ = s; + return s; + } + } + /** + * string message_json = 1; + * @return The bytes for messageJson. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getMessageJsonBytes() { + java.lang.Object ref = messageJson_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + messageJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(messageJson_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, messageJson_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(messageJson_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, messageJson_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage other = (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) obj; + + if (!getMessageJson() + .equals(other.getMessageJson())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + MESSAGE_JSON_FIELD_NUMBER; + hash = (53 * hash) + getMessageJson().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentChatMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentChatMessage) + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentChatMessage_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentChatMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.class, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + messageJson_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentChatMessage_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage build() { + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage result = new org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.messageJson_ = messageJson_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance()) return this; + if (!other.getMessageJson().isEmpty()) { + messageJson_ = other.messageJson_; + bitField0_ |= 0x00000001; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + messageJson_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object messageJson_ = ""; + /** + * string message_json = 1; + * @return The messageJson. + */ + public java.lang.String getMessageJson() { + java.lang.Object ref = messageJson_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + messageJson_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string message_json = 1; + * @return The bytes for messageJson. + */ + public com.google.protobuf.ByteString + getMessageJsonBytes() { + java.lang.Object ref = messageJson_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + messageJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string message_json = 1; + * @param value The messageJson to set. + * @return This builder for chaining. + */ + public Builder setMessageJson( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + messageJson_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string message_json = 1; + * @return This builder for chaining. + */ + public Builder clearMessageJson() { + messageJson_ = getDefaultInstance().getMessageJson(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string message_json = 1; + * @param value The bytes for messageJson to set. + * @return This builder for chaining. + */ + public Builder setMessageJsonBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + messageJson_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentChatMessage) + } + + // @@protoc_insertion_point(class_scope:message.AgentChatMessage) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentChatMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentToolCallOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentToolCall) + com.google.protobuf.MessageOrBuilder { + + /** + * string id = 1; + * @return The id. + */ + java.lang.String getId(); + /** + * string id = 1; + * @return The bytes for id. + */ + com.google.protobuf.ByteString + getIdBytes(); + + /** + * string name = 2; + * @return The name. + */ + java.lang.String getName(); + /** + * string name = 2; + * @return The bytes for name. + */ + com.google.protobuf.ByteString + getNameBytes(); + + /** + * string arguments_json = 3; + * @return The argumentsJson. + */ + java.lang.String getArgumentsJson(); + /** + * string arguments_json = 3; + * @return The bytes for argumentsJson. + */ + com.google.protobuf.ByteString + getArgumentsJsonBytes(); + } + /** + * Protobuf type {@code message.AgentToolCall} + */ + public static final class AgentToolCall extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentToolCall) + AgentToolCallOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentToolCall.class.getName()); + } + // Use AgentToolCall.newBuilder() to construct. + private AgentToolCall(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentToolCall() { + id_ = ""; + name_ = ""; + argumentsJson_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolCall_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolCall_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.class, org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.Builder.class); + } + + public static final int ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object id_ = ""; + /** + * string id = 1; + * @return The id. + */ + @java.lang.Override + public java.lang.String getId() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } + } + /** + * string id = 1; + * @return The bytes for id. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int NAME_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object name_ = ""; + /** + * string name = 2; + * @return The name. + */ + @java.lang.Override + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } + } + /** + * string name = 2; + * @return The bytes for name. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ARGUMENTS_JSON_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object argumentsJson_ = ""; + /** + * string arguments_json = 3; + * @return The argumentsJson. + */ + @java.lang.Override + public java.lang.String getArgumentsJson() { + java.lang.Object ref = argumentsJson_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + argumentsJson_ = s; + return s; + } + } + /** + * string arguments_json = 3; + * @return The bytes for argumentsJson. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getArgumentsJsonBytes() { + java.lang.Object ref = argumentsJson_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + argumentsJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(id_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(name_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, name_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(argumentsJson_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, argumentsJson_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(id_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, id_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(name_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, name_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(argumentsJson_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, argumentsJson_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentToolCall)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall other = (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) obj; + + if (!getId() + .equals(other.getId())) return false; + if (!getName() + .equals(other.getName())) return false; + if (!getArgumentsJson() + .equals(other.getArgumentsJson())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId().hashCode(); + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + hash = (37 * hash) + ARGUMENTS_JSON_FIELD_NUMBER; + hash = (53 * hash) + getArgumentsJson().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentToolCall prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentToolCall} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentToolCall) + org.jumpserver.wisp.ServiceOuterClass.AgentToolCallOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolCall_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolCall_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.class, org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = ""; + name_ = ""; + argumentsJson_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentToolCall_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCall getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCall build() { + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCall buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall result = new org.jumpserver.wisp.ServiceOuterClass.AgentToolCall(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentToolCall result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.name_ = name_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.argumentsJson_ = argumentsJson_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentToolCall)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentToolCall other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance()) return this; + if (!other.getId().isEmpty()) { + id_ = other.id_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (!other.getName().isEmpty()) { + name_ = other.name_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (!other.getArgumentsJson().isEmpty()) { + argumentsJson_ = other.argumentsJson_; + bitField0_ |= 0x00000004; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + id_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + name_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + argumentsJson_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 26 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object id_ = ""; + /** + * string id = 1; + * @return The id. + */ + public java.lang.String getId() { + java.lang.Object ref = id_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string id = 1; + * @return The bytes for id. + */ + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string id = 1; + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string id = 1; + * @return This builder for chaining. + */ + public Builder clearId() { + id_ = getDefaultInstance().getId(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string id = 1; + * @param value The bytes for id to set. + * @return This builder for chaining. + */ + public Builder setIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private java.lang.Object name_ = ""; + /** + * string name = 2; + * @return The name. + */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string name = 2; + * @return The bytes for name. + */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string name = 2; + * @param value The name to set. + * @return This builder for chaining. + */ + public Builder setName( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * string name = 2; + * @return This builder for chaining. + */ + public Builder clearName() { + name_ = getDefaultInstance().getName(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * string name = 2; + * @param value The bytes for name to set. + * @return This builder for chaining. + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private java.lang.Object argumentsJson_ = ""; + /** + * string arguments_json = 3; + * @return The argumentsJson. + */ + public java.lang.String getArgumentsJson() { + java.lang.Object ref = argumentsJson_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + argumentsJson_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string arguments_json = 3; + * @return The bytes for argumentsJson. + */ + public com.google.protobuf.ByteString + getArgumentsJsonBytes() { + java.lang.Object ref = argumentsJson_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + argumentsJson_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string arguments_json = 3; + * @param value The argumentsJson to set. + * @return This builder for chaining. + */ + public Builder setArgumentsJson( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + argumentsJson_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * string arguments_json = 3; + * @return This builder for chaining. + */ + public Builder clearArgumentsJson() { + argumentsJson_ = getDefaultInstance().getArgumentsJson(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + /** + * string arguments_json = 3; + * @param value The bytes for argumentsJson to set. + * @return This builder for chaining. + */ + public Builder setArgumentsJsonBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + argumentsJson_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentToolCall) + } + + // @@protoc_insertion_point(class_scope:message.AgentToolCall) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentToolCall DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentToolCall(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentToolCall getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentToolCall parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCall getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentErrorOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentError) + com.google.protobuf.MessageOrBuilder { + + /** + * string code = 1; + * @return The code. + */ + java.lang.String getCode(); + /** + * string code = 1; + * @return The bytes for code. + */ + com.google.protobuf.ByteString + getCodeBytes(); + + /** + * string message = 2; + * @return The message. + */ + java.lang.String getMessage(); + /** + * string message = 2; + * @return The bytes for message. + */ + com.google.protobuf.ByteString + getMessageBytes(); + + /** + * string request_id = 3; + * @return The requestId. + */ + java.lang.String getRequestId(); + /** + * string request_id = 3; + * @return The bytes for requestId. + */ + com.google.protobuf.ByteString + getRequestIdBytes(); + } + /** + * Protobuf type {@code message.AgentError} + */ + public static final class AgentError extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentError) + AgentErrorOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentError.class.getName()); + } + // Use AgentError.newBuilder() to construct. + private AgentError(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentError() { + code_ = ""; + message_ = ""; + requestId_ = ""; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentError_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentError_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentError.class, org.jumpserver.wisp.ServiceOuterClass.AgentError.Builder.class); + } + + public static final int CODE_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object code_ = ""; + /** + * string code = 1; + * @return The code. + */ + @java.lang.Override + public java.lang.String getCode() { + java.lang.Object ref = code_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + code_ = s; + return s; + } + } + /** + * string code = 1; + * @return The bytes for code. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getCodeBytes() { + java.lang.Object ref = code_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + code_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int MESSAGE_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object message_ = ""; + /** + * string message = 2; + * @return The message. + */ + @java.lang.Override + public java.lang.String getMessage() { + java.lang.Object ref = message_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + message_ = s; + return s; + } + } + /** + * string message = 2; + * @return The bytes for message. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getMessageBytes() { + java.lang.Object ref = message_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + message_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int REQUEST_ID_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object requestId_ = ""; + /** + * string request_id = 3; + * @return The requestId. + */ + @java.lang.Override + public java.lang.String getRequestId() { + java.lang.Object ref = requestId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + requestId_ = s; + return s; + } + } + /** + * string request_id = 3; + * @return The bytes for requestId. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getRequestIdBytes() { + java.lang.Object ref = requestId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + requestId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(code_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 1, code_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(message_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 2, message_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(requestId_)) { + com.google.protobuf.GeneratedMessage.writeString(output, 3, requestId_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(code_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(1, code_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(message_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(2, message_); + } + if (!com.google.protobuf.GeneratedMessage.isStringEmpty(requestId_)) { + size += com.google.protobuf.GeneratedMessage.computeStringSize(3, requestId_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentError)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentError other = (org.jumpserver.wisp.ServiceOuterClass.AgentError) obj; + + if (!getCode() + .equals(other.getCode())) return false; + if (!getMessage() + .equals(other.getMessage())) return false; + if (!getRequestId() + .equals(other.getRequestId())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + CODE_FIELD_NUMBER; + hash = (53 * hash) + getCode().hashCode(); + hash = (37 * hash) + MESSAGE_FIELD_NUMBER; + hash = (53 * hash) + getMessage().hashCode(); + hash = (37 * hash) + REQUEST_ID_FIELD_NUMBER; + hash = (53 * hash) + getRequestId().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentError parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentError prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentError} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentError) + org.jumpserver.wisp.ServiceOuterClass.AgentErrorOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentError_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentError_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentError.class, org.jumpserver.wisp.ServiceOuterClass.AgentError.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentError.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + code_ = ""; + message_ = ""; + requestId_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentError_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentError getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentError build() { + org.jumpserver.wisp.ServiceOuterClass.AgentError result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentError buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentError result = new org.jumpserver.wisp.ServiceOuterClass.AgentError(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentError result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.code_ = code_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.message_ = message_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.requestId_ = requestId_; + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentError) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentError)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentError other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance()) return this; + if (!other.getCode().isEmpty()) { + code_ = other.code_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (!other.getMessage().isEmpty()) { + message_ = other.message_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (!other.getRequestId().isEmpty()) { + requestId_ = other.requestId_; + bitField0_ |= 0x00000004; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + code_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + message_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + requestId_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 26 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object code_ = ""; + /** + * string code = 1; + * @return The code. + */ + public java.lang.String getCode() { + java.lang.Object ref = code_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + code_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string code = 1; + * @return The bytes for code. + */ + public com.google.protobuf.ByteString + getCodeBytes() { + java.lang.Object ref = code_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + code_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string code = 1; + * @param value The code to set. + * @return This builder for chaining. + */ + public Builder setCode( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + code_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * string code = 1; + * @return This builder for chaining. + */ + public Builder clearCode() { + code_ = getDefaultInstance().getCode(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * string code = 1; + * @param value The bytes for code to set. + * @return This builder for chaining. + */ + public Builder setCodeBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + code_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private java.lang.Object message_ = ""; + /** + * string message = 2; + * @return The message. + */ + public java.lang.String getMessage() { + java.lang.Object ref = message_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + message_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string message = 2; + * @return The bytes for message. + */ + public com.google.protobuf.ByteString + getMessageBytes() { + java.lang.Object ref = message_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + message_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string message = 2; + * @param value The message to set. + * @return This builder for chaining. + */ + public Builder setMessage( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + message_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * string message = 2; + * @return This builder for chaining. + */ + public Builder clearMessage() { + message_ = getDefaultInstance().getMessage(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * string message = 2; + * @param value The bytes for message to set. + * @return This builder for chaining. + */ + public Builder setMessageBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + message_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private java.lang.Object requestId_ = ""; + /** + * string request_id = 3; + * @return The requestId. + */ + public java.lang.String getRequestId() { + java.lang.Object ref = requestId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + requestId_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string request_id = 3; + * @return The bytes for requestId. + */ + public com.google.protobuf.ByteString + getRequestIdBytes() { + java.lang.Object ref = requestId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + requestId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string request_id = 3; + * @param value The requestId to set. + * @return This builder for chaining. + */ + public Builder setRequestId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + requestId_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * string request_id = 3; + * @return This builder for chaining. + */ + public Builder clearRequestId() { + requestId_ = getDefaultInstance().getRequestId(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + /** + * string request_id = 3; + * @param value The bytes for requestId to set. + * @return This builder for chaining. + */ + public Builder setRequestIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + requestId_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:message.AgentError) + } + + // @@protoc_insertion_point(class_scope:message.AgentError) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentError DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentError(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentError getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentError parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentError getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AgentServerEventOrBuilder extends + // @@protoc_insertion_point(interface_extends:message.AgentServerEvent) + com.google.protobuf.MessageOrBuilder { + + /** + * .message.AgentReady ready = 1; + * @return Whether the ready field is set. + */ + boolean hasReady(); + /** + * .message.AgentReady ready = 1; + * @return The ready. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentReady getReady(); + /** + * .message.AgentReady ready = 1; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentReadyOrBuilder getReadyOrBuilder(); + + /** + * .message.AgentChatMessage chat = 2; + * @return Whether the chat field is set. + */ + boolean hasChat(); + /** + * .message.AgentChatMessage chat = 2; + * @return The chat. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage getChat(); + /** + * .message.AgentChatMessage chat = 2; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessageOrBuilder getChatOrBuilder(); + + /** + * .message.AgentToolCall tool_call = 3; + * @return Whether the toolCall field is set. + */ + boolean hasToolCall(); + /** + * .message.AgentToolCall tool_call = 3; + * @return The toolCall. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall getToolCall(); + /** + * .message.AgentToolCall tool_call = 3; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentToolCallOrBuilder getToolCallOrBuilder(); + + /** + * .message.AgentError error = 4; + * @return Whether the error field is set. + */ + boolean hasError(); + /** + * .message.AgentError error = 4; + * @return The error. + */ + org.jumpserver.wisp.ServiceOuterClass.AgentError getError(); + /** + * .message.AgentError error = 4; + */ + org.jumpserver.wisp.ServiceOuterClass.AgentErrorOrBuilder getErrorOrBuilder(); + + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.EventCase getEventCase(); + } + /** + * Protobuf type {@code message.AgentServerEvent} + */ + public static final class AgentServerEvent extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:message.AgentServerEvent) + AgentServerEventOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 32, + /* patch= */ 1, + /* suffix= */ "", + AgentServerEvent.class.getName()); + } + // Use AgentServerEvent.newBuilder() to construct. + private AgentServerEvent(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + } + private AgentServerEvent() { + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentServerEvent_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentServerEvent_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.class, org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.Builder.class); + } + + private int eventCase_ = 0; + @SuppressWarnings("serial") + private java.lang.Object event_; + public enum EventCase + implements com.google.protobuf.Internal.EnumLite, + com.google.protobuf.AbstractMessage.InternalOneOfEnum { + READY(1), + CHAT(2), + TOOL_CALL(3), + ERROR(4), + EVENT_NOT_SET(0); + private final int value; + private EventCase(int value) { + this.value = value; + } + /** + * @param value The number of the enum to look for. + * @return The enum associated with the given number. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static EventCase valueOf(int value) { + return forNumber(value); + } + + public static EventCase forNumber(int value) { + switch (value) { + case 1: return READY; + case 2: return CHAT; + case 3: return TOOL_CALL; + case 4: return ERROR; + case 0: return EVENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public EventCase + getEventCase() { + return EventCase.forNumber( + eventCase_); + } + + public static final int READY_FIELD_NUMBER = 1; + /** + * .message.AgentReady ready = 1; + * @return Whether the ready field is set. + */ + @java.lang.Override + public boolean hasReady() { + return eventCase_ == 1; + } + /** + * .message.AgentReady ready = 1; + * @return The ready. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReady getReady() { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance(); + } + /** + * .message.AgentReady ready = 1; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReadyOrBuilder getReadyOrBuilder() { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance(); + } + + public static final int CHAT_FIELD_NUMBER = 2; + /** + * .message.AgentChatMessage chat = 2; + * @return Whether the chat field is set. + */ + @java.lang.Override + public boolean hasChat() { + return eventCase_ == 2; + } + /** + * .message.AgentChatMessage chat = 2; + * @return The chat. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage getChat() { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance(); + } + /** + * .message.AgentChatMessage chat = 2; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessageOrBuilder getChatOrBuilder() { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance(); + } + + public static final int TOOL_CALL_FIELD_NUMBER = 3; + /** + * .message.AgentToolCall tool_call = 3; + * @return Whether the toolCall field is set. + */ + @java.lang.Override + public boolean hasToolCall() { + return eventCase_ == 3; + } + /** + * .message.AgentToolCall tool_call = 3; + * @return The toolCall. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCall getToolCall() { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance(); + } + /** + * .message.AgentToolCall tool_call = 3; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCallOrBuilder getToolCallOrBuilder() { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance(); + } + + public static final int ERROR_FIELD_NUMBER = 4; + /** + * .message.AgentError error = 4; + * @return Whether the error field is set. + */ + @java.lang.Override + public boolean hasError() { + return eventCase_ == 4; + } + /** + * .message.AgentError error = 4; + * @return The error. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentError getError() { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentError) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance(); + } + /** + * .message.AgentError error = 4; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentErrorOrBuilder getErrorOrBuilder() { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentError) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (eventCase_ == 1) { + output.writeMessage(1, (org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_); + } + if (eventCase_ == 2) { + output.writeMessage(2, (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_); + } + if (eventCase_ == 3) { + output.writeMessage(3, (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_); + } + if (eventCase_ == 4) { + output.writeMessage(4, (org.jumpserver.wisp.ServiceOuterClass.AgentError) event_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (eventCase_ == 1) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, (org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_); + } + if (eventCase_ == 2) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_); + } + if (eventCase_ == 3) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_); + } + if (eventCase_ == 4) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(4, (org.jumpserver.wisp.ServiceOuterClass.AgentError) event_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent)) { + return super.equals(obj); + } + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent other = (org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent) obj; + + if (!getEventCase().equals(other.getEventCase())) return false; + switch (eventCase_) { + case 1: + if (!getReady() + .equals(other.getReady())) return false; + break; + case 2: + if (!getChat() + .equals(other.getChat())) return false; + break; + case 3: + if (!getToolCall() + .equals(other.getToolCall())) return false; + break; + case 4: + if (!getError() + .equals(other.getError())) return false; + break; + case 0: + default: + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + switch (eventCase_) { + case 1: + hash = (37 * hash) + READY_FIELD_NUMBER; + hash = (53 * hash) + getReady().hashCode(); + break; + case 2: + hash = (37 * hash) + CHAT_FIELD_NUMBER; + hash = (53 * hash) + getChat().hashCode(); + break; + case 3: + hash = (37 * hash) + TOOL_CALL_FIELD_NUMBER; + hash = (53 * hash) + getToolCall().hashCode(); + break; + case 4: + hash = (37 * hash) + ERROR_FIELD_NUMBER; + hash = (53 * hash) + getError().hashCode(); + break; + case 0: + default: + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code message.AgentServerEvent} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements + // @@protoc_insertion_point(builder_implements:message.AgentServerEvent) + org.jumpserver.wisp.ServiceOuterClass.AgentServerEventOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentServerEvent_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentServerEvent_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.class, org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.Builder.class); + } + + // Construct using org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (readyBuilder_ != null) { + readyBuilder_.clear(); + } + if (chatBuilder_ != null) { + chatBuilder_.clear(); + } + if (toolCallBuilder_ != null) { + toolCallBuilder_.clear(); + } + if (errorBuilder_ != null) { + errorBuilder_.clear(); + } + eventCase_ = 0; + event_ = null; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.jumpserver.wisp.ServiceOuterClass.internal_static_message_AgentServerEvent_descriptor; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent getDefaultInstanceForType() { + return org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.getDefaultInstance(); + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent build() { + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent buildPartial() { + org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent result = new org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent(this); + if (bitField0_ != 0) { buildPartial0(result); } + buildPartialOneofs(result); + onBuilt(); + return result; + } + + private void buildPartial0(org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent result) { + int from_bitField0_ = bitField0_; + } + + private void buildPartialOneofs(org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent result) { + result.eventCase_ = eventCase_; + result.event_ = this.event_; + if (eventCase_ == 1 && + readyBuilder_ != null) { + result.event_ = readyBuilder_.build(); + } + if (eventCase_ == 2 && + chatBuilder_ != null) { + result.event_ = chatBuilder_.build(); + } + if (eventCase_ == 3 && + toolCallBuilder_ != null) { + result.event_ = toolCallBuilder_.build(); + } + if (eventCase_ == 4 && + errorBuilder_ != null) { + result.event_ = errorBuilder_.build(); + } + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent) { + return mergeFrom((org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent other) { + if (other == org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent.getDefaultInstance()) return this; + switch (other.getEventCase()) { + case READY: { + mergeReady(other.getReady()); + break; + } + case CHAT: { + mergeChat(other.getChat()); + break; + } + case TOOL_CALL: { + mergeToolCall(other.getToolCall()); + break; + } + case ERROR: { + mergeError(other.getError()); + break; + } + case EVENT_NOT_SET: { + break; + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + input.readMessage( + internalGetReadyFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 1; + break; + } // case 10 + case 18: { + input.readMessage( + internalGetChatFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 2; + break; + } // case 18 + case 26: { + input.readMessage( + internalGetToolCallFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 3; + break; + } // case 26 + case 34: { + input.readMessage( + internalGetErrorFieldBuilder().getBuilder(), + extensionRegistry); + eventCase_ = 4; + break; + } // case 34 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int eventCase_ = 0; + private java.lang.Object event_; + public EventCase + getEventCase() { + return EventCase.forNumber( + eventCase_); + } + + public Builder clearEvent() { + eventCase_ = 0; + event_ = null; + onChanged(); + return this; + } + + private int bitField0_; + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentReady, org.jumpserver.wisp.ServiceOuterClass.AgentReady.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentReadyOrBuilder> readyBuilder_; + /** + * .message.AgentReady ready = 1; + * @return Whether the ready field is set. + */ + @java.lang.Override + public boolean hasReady() { + return eventCase_ == 1; + } + /** + * .message.AgentReady ready = 1; + * @return The ready. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReady getReady() { + if (readyBuilder_ == null) { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance(); + } else { + if (eventCase_ == 1) { + return readyBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance(); + } + } + /** + * .message.AgentReady ready = 1; + */ + public Builder setReady(org.jumpserver.wisp.ServiceOuterClass.AgentReady value) { + if (readyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + readyBuilder_.setMessage(value); + } + eventCase_ = 1; + return this; + } + /** + * .message.AgentReady ready = 1; + */ + public Builder setReady( + org.jumpserver.wisp.ServiceOuterClass.AgentReady.Builder builderForValue) { + if (readyBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + readyBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 1; + return this; + } + /** + * .message.AgentReady ready = 1; + */ + public Builder mergeReady(org.jumpserver.wisp.ServiceOuterClass.AgentReady value) { + if (readyBuilder_ == null) { + if (eventCase_ == 1 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentReady.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 1) { + readyBuilder_.mergeFrom(value); + } else { + readyBuilder_.setMessage(value); + } + } + eventCase_ = 1; + return this; + } + /** + * .message.AgentReady ready = 1; + */ + public Builder clearReady() { + if (readyBuilder_ == null) { + if (eventCase_ == 1) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 1) { + eventCase_ = 0; + event_ = null; + } + readyBuilder_.clear(); + } + return this; + } + /** + * .message.AgentReady ready = 1; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentReady.Builder getReadyBuilder() { + return internalGetReadyFieldBuilder().getBuilder(); + } + /** + * .message.AgentReady ready = 1; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentReadyOrBuilder getReadyOrBuilder() { + if ((eventCase_ == 1) && (readyBuilder_ != null)) { + return readyBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 1) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance(); + } + } + /** + * .message.AgentReady ready = 1; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentReady, org.jumpserver.wisp.ServiceOuterClass.AgentReady.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentReadyOrBuilder> + internalGetReadyFieldBuilder() { + if (readyBuilder_ == null) { + if (!(eventCase_ == 1)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentReady.getDefaultInstance(); + } + readyBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentReady, org.jumpserver.wisp.ServiceOuterClass.AgentReady.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentReadyOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentReady) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 1; + onChanged(); + return readyBuilder_; + } + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessageOrBuilder> chatBuilder_; + /** + * .message.AgentChatMessage chat = 2; + * @return Whether the chat field is set. + */ + @java.lang.Override + public boolean hasChat() { + return eventCase_ == 2; + } + /** + * .message.AgentChatMessage chat = 2; + * @return The chat. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage getChat() { + if (chatBuilder_ == null) { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance(); + } else { + if (eventCase_ == 2) { + return chatBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance(); + } + } + /** + * .message.AgentChatMessage chat = 2; + */ + public Builder setChat(org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage value) { + if (chatBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + chatBuilder_.setMessage(value); + } + eventCase_ = 2; + return this; + } + /** + * .message.AgentChatMessage chat = 2; + */ + public Builder setChat( + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.Builder builderForValue) { + if (chatBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + chatBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 2; + return this; + } + /** + * .message.AgentChatMessage chat = 2; + */ + public Builder mergeChat(org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage value) { + if (chatBuilder_ == null) { + if (eventCase_ == 2 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 2) { + chatBuilder_.mergeFrom(value); + } else { + chatBuilder_.setMessage(value); + } + } + eventCase_ = 2; + return this; + } + /** + * .message.AgentChatMessage chat = 2; + */ + public Builder clearChat() { + if (chatBuilder_ == null) { + if (eventCase_ == 2) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 2) { + eventCase_ = 0; + event_ = null; + } + chatBuilder_.clear(); + } + return this; + } + /** + * .message.AgentChatMessage chat = 2; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.Builder getChatBuilder() { + return internalGetChatFieldBuilder().getBuilder(); + } + /** + * .message.AgentChatMessage chat = 2; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentChatMessageOrBuilder getChatOrBuilder() { + if ((eventCase_ == 2) && (chatBuilder_ != null)) { + return chatBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 2) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance(); + } + } + /** + * .message.AgentChatMessage chat = 2; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessageOrBuilder> + internalGetChatFieldBuilder() { + if (chatBuilder_ == null) { + if (!(eventCase_ == 2)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.getDefaultInstance(); + } + chatBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentChatMessageOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentChatMessage) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 2; + onChanged(); + return chatBuilder_; + } + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall, org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentToolCallOrBuilder> toolCallBuilder_; + /** + * .message.AgentToolCall tool_call = 3; + * @return Whether the toolCall field is set. + */ + @java.lang.Override + public boolean hasToolCall() { + return eventCase_ == 3; + } + /** + * .message.AgentToolCall tool_call = 3; + * @return The toolCall. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCall getToolCall() { + if (toolCallBuilder_ == null) { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance(); + } else { + if (eventCase_ == 3) { + return toolCallBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance(); + } + } + /** + * .message.AgentToolCall tool_call = 3; + */ + public Builder setToolCall(org.jumpserver.wisp.ServiceOuterClass.AgentToolCall value) { + if (toolCallBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + toolCallBuilder_.setMessage(value); + } + eventCase_ = 3; + return this; + } + /** + * .message.AgentToolCall tool_call = 3; + */ + public Builder setToolCall( + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.Builder builderForValue) { + if (toolCallBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + toolCallBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 3; + return this; + } + /** + * .message.AgentToolCall tool_call = 3; + */ + public Builder mergeToolCall(org.jumpserver.wisp.ServiceOuterClass.AgentToolCall value) { + if (toolCallBuilder_ == null) { + if (eventCase_ == 3 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 3) { + toolCallBuilder_.mergeFrom(value); + } else { + toolCallBuilder_.setMessage(value); + } + } + eventCase_ = 3; + return this; + } + /** + * .message.AgentToolCall tool_call = 3; + */ + public Builder clearToolCall() { + if (toolCallBuilder_ == null) { + if (eventCase_ == 3) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 3) { + eventCase_ = 0; + event_ = null; + } + toolCallBuilder_.clear(); + } + return this; + } + /** + * .message.AgentToolCall tool_call = 3; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.Builder getToolCallBuilder() { + return internalGetToolCallFieldBuilder().getBuilder(); + } + /** + * .message.AgentToolCall tool_call = 3; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentToolCallOrBuilder getToolCallOrBuilder() { + if ((eventCase_ == 3) && (toolCallBuilder_ != null)) { + return toolCallBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 3) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance(); + } + } + /** + * .message.AgentToolCall tool_call = 3; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall, org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentToolCallOrBuilder> + internalGetToolCallFieldBuilder() { + if (toolCallBuilder_ == null) { + if (!(eventCase_ == 3)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.getDefaultInstance(); + } + toolCallBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentToolCall, org.jumpserver.wisp.ServiceOuterClass.AgentToolCall.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentToolCallOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentToolCall) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 3; + onChanged(); + return toolCallBuilder_; + } + + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentError, org.jumpserver.wisp.ServiceOuterClass.AgentError.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentErrorOrBuilder> errorBuilder_; + /** + * .message.AgentError error = 4; + * @return Whether the error field is set. + */ + @java.lang.Override + public boolean hasError() { + return eventCase_ == 4; + } + /** + * .message.AgentError error = 4; + * @return The error. + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentError getError() { + if (errorBuilder_ == null) { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentError) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance(); + } else { + if (eventCase_ == 4) { + return errorBuilder_.getMessage(); + } + return org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance(); + } + } + /** + * .message.AgentError error = 4; + */ + public Builder setError(org.jumpserver.wisp.ServiceOuterClass.AgentError value) { + if (errorBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + event_ = value; + onChanged(); + } else { + errorBuilder_.setMessage(value); + } + eventCase_ = 4; + return this; + } + /** + * .message.AgentError error = 4; + */ + public Builder setError( + org.jumpserver.wisp.ServiceOuterClass.AgentError.Builder builderForValue) { + if (errorBuilder_ == null) { + event_ = builderForValue.build(); + onChanged(); + } else { + errorBuilder_.setMessage(builderForValue.build()); + } + eventCase_ = 4; + return this; + } + /** + * .message.AgentError error = 4; + */ + public Builder mergeError(org.jumpserver.wisp.ServiceOuterClass.AgentError value) { + if (errorBuilder_ == null) { + if (eventCase_ == 4 && + event_ != org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance()) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentError.newBuilder((org.jumpserver.wisp.ServiceOuterClass.AgentError) event_) + .mergeFrom(value).buildPartial(); + } else { + event_ = value; + } + onChanged(); + } else { + if (eventCase_ == 4) { + errorBuilder_.mergeFrom(value); + } else { + errorBuilder_.setMessage(value); + } + } + eventCase_ = 4; + return this; + } + /** + * .message.AgentError error = 4; + */ + public Builder clearError() { + if (errorBuilder_ == null) { + if (eventCase_ == 4) { + eventCase_ = 0; + event_ = null; + onChanged(); + } + } else { + if (eventCase_ == 4) { + eventCase_ = 0; + event_ = null; + } + errorBuilder_.clear(); + } + return this; + } + /** + * .message.AgentError error = 4; + */ + public org.jumpserver.wisp.ServiceOuterClass.AgentError.Builder getErrorBuilder() { + return internalGetErrorFieldBuilder().getBuilder(); + } + /** + * .message.AgentError error = 4; + */ + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentErrorOrBuilder getErrorOrBuilder() { + if ((eventCase_ == 4) && (errorBuilder_ != null)) { + return errorBuilder_.getMessageOrBuilder(); + } else { + if (eventCase_ == 4) { + return (org.jumpserver.wisp.ServiceOuterClass.AgentError) event_; + } + return org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance(); + } + } + /** + * .message.AgentError error = 4; + */ + private com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentError, org.jumpserver.wisp.ServiceOuterClass.AgentError.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentErrorOrBuilder> + internalGetErrorFieldBuilder() { + if (errorBuilder_ == null) { + if (!(eventCase_ == 4)) { + event_ = org.jumpserver.wisp.ServiceOuterClass.AgentError.getDefaultInstance(); + } + errorBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.jumpserver.wisp.ServiceOuterClass.AgentError, org.jumpserver.wisp.ServiceOuterClass.AgentError.Builder, org.jumpserver.wisp.ServiceOuterClass.AgentErrorOrBuilder>( + (org.jumpserver.wisp.ServiceOuterClass.AgentError) event_, + getParentForChildren(), + isClean()); + event_ = null; + } + eventCase_ = 4; + onChanged(); + return errorBuilder_; + } + + // @@protoc_insertion_point(builder_scope:message.AgentServerEvent) + } + + // @@protoc_insertion_point(class_scope:message.AgentServerEvent) + private static final org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent(); + } + + public static org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AgentServerEvent parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.jumpserver.wisp.ServiceOuterClass.AgentServerEvent getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_JoinFaceMonitorRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_JoinFaceMonitorRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_JoinFaceMonitorResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_JoinFaceMonitorResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceMonitorCallbackRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceMonitorCallbackRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceMonitorCallbackResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceMonitorCallbackResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceRecognitionCallbackRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceRecognitionCallbackRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FaceRecognitionCallbackResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FaceRecognitionCallbackResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_AssetLoginTicketRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_AssetLoginTicketRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_AssetLoginTicketResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_AssetLoginTicketResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Status_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Status_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TokenRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TokenRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TokenResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TokenResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionCreateRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionCreateRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionCreateResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionCreateResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionFinishRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionFinishRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionFinishResp_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionFinishResp_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ReplayRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ReplayRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ReplayResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ReplayResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_FinishedTaskRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_FinishedTaskRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TaskResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TaskResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_RemainReplayRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_RemainReplayRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_RemainReplayResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_RemainReplayResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_StatusResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_StatusResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandConfirmRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandConfirmRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ReqInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ReqInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CommandConfirmResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CommandConfirmResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketStateResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketStateResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_TicketState_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_TicketState_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ForwardRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ForwardRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ForwardDeleteRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ForwardDeleteRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ForwardResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ForwardResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PublicSettingResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PublicSettingResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_Empty_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_Empty_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_ListenPortResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_ListenPortResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortInfoRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortInfoRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortInfoResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortInfoResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortInfo_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortInfo_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortFailure_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortFailure_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_PortFailureRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_PortFailureRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_CookiesRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_CookiesRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_UserResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_UserResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_SessionLifecycleLogRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_SessionLifecycleLogRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_AccountDetailResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_AccountDetailResponse_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPRequest_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPRequest_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPRequest_QueryEntry_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPRequest_QueryEntry_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPRequest_HeaderEntry_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPRequest_HeaderEntry_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_message_HTTPResponse_descriptor; - private static final + private static final com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_message_HTTPResponse_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentSessionOpen_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentSessionOpen_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentRequest_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentRequest_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentToolResult_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentToolResult_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentCancel_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentCancel_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentClientEvent_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentClientEvent_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentReady_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentReady_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentChatMessage_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentChatMessage_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentToolCall_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentToolCall_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentError_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentError_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_message_AgentServerEvent_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_message_AgentServerEvent_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -36820,57 +46882,86 @@ public org.jumpserver.wisp.ServiceOuterClass.HTTPResponse getDefaultInstanceForT "\030\002 \001(\t:\0028\001\032-\n\013HeaderEntry\022\013\n\003key\030\001 \001(\t\022\r" + "\n\005value\030\002 \001(\t:\0028\001\"=\n\014HTTPResponse\022\037\n\006sta" + "tus\030\001 \001(\0132\017.message.Status\022\014\n\004body\030\002 \001(\014" + - "2\245\017\n\007Service\022C\n\020GetTokenAuthInfo\022\025.messa" + - "ge.TokenRequest\032\026.message.TokenResponse\"" + - "\000\022>\n\nRenewToken\022\025.message.TokenRequest\032\027" + - ".message.StatusResponse\"\000\022P\n\rCreateSessi" + - "on\022\035.message.SessionCreateRequest\032\036.mess" + - "age.SessionCreateResponse\"\000\022L\n\rFinishSes" + - "sion\022\035.message.SessionFinishRequest\032\032.me" + - "ssage.SessionFinishResp\"\000\022E\n\020UploadRepla" + - "yFile\022\026.message.ReplayRequest\032\027.message." + - "ReplayResponse\"\000\022D\n\rUploadCommand\022\027.mess" + - "age.CommandRequest\032\030.message.CommandResp" + - "onse\"\000\022I\n\014DispatchTask\022\034.message.Finishe" + - "dTaskRequest\032\025.message.TaskResponse\"\000(\0010" + - "\001\022R\n\021ScanRemainReplays\022\034.message.RemainR" + - "eplayRequest\032\035.message.RemainReplayRespo" + - "nse\"\000\022X\n\023CreateCommandTicket\022\036.message.C" + - "ommandConfirmRequest\032\037.message.CommandCo" + - "nfirmResponse\"\000\022f\n\035CheckOrCreateAssetLog" + - "inTicket\022 .message.AssetLoginTicketReque" + - "st\032!.message.AssetLoginTicketResponse\"\000\022" + - "J\n\020CheckTicketState\022\026.message.TicketRequ" + - "est\032\034.message.TicketStateResponse\"\000\022A\n\014C" + - "ancelTicket\022\026.message.TicketRequest\032\027.me" + - "ssage.StatusResponse\"\000\022D\n\rCreateForward\022" + - "\027.message.ForwardRequest\032\030.message.Forwa" + - "rdResponse\"\000\022I\n\rDeleteForward\022\035.message." + - "ForwardDeleteRequest\032\027.message.StatusRes" + - "ponse\"\000\022D\n\020GetPublicSetting\022\016.message.Em" + - "pty\032\036.message.PublicSettingResponse\"\000\022?\n" + - "\016GetListenPorts\022\016.message.Empty\032\033.messag" + - "e.ListenPortResponse\"\000\022D\n\013GetPortInfo\022\030." + - "message.PortInfoRequest\032\031.message.PortIn" + - "foResponse\"\000\022K\n\021HandlePortFailure\022\033.mess" + - "age.PortFailureRequest\032\027.message.StatusR" + - "esponse\"\000\022F\n\022CheckUserByCookies\022\027.messag" + - "e.CookiesRequest\032\025.message.UserResponse\"" + - "\000\022[\n\031RecordSessionLifecycleLog\022#.message" + - ".SessionLifecycleLogRequest\032\027.message.St" + - "atusResponse\"\000\022n\n\027FaceRecognitionCallbac" + - "k\022\'.message.FaceRecognitionCallbackReque" + - "st\032(.message.FaceRecognitionCallbackResp" + - "onse\"\000\022b\n\023FaceMonitorCallback\022#.message." + - "FaceMonitorCallbackRequest\032$.message.Fac" + - "eMonitorCallbackResponse\"\000\022V\n\017JoinFaceMo" + - "nitor\022\037.message.JoinFaceMonitorRequest\032 " + - ".message.JoinFaceMonitorResponse\"\000\022B\n\016Ge" + - "tAccountChat\022\016.message.Empty\032\036.message.A" + - "ccountDetailResponse\"\000\0228\n\007CallAPI\022\024.mess" + - "age.HTTPRequest\032\025.message.HTTPResponse\"\000" + - "B \n\023org.jumpserver.wispZ\t/protobufb\006prot" + - "o3" + "\"\253\001\n\020AgentSessionOpen\022\022\n\nsession_id\030\001 \001(" + + "\t\022\017\n\007user_id\030\002 \001(\t\022\027\n\017organization_id\030\003 " + + "\001(\t\022\020\n\010asset_id\030\004 \001(\t\022\022\n\naccount_id\030\005 \001(" + + "\t\022\020\n\010protocol\030\006 \001(\t\022\020\n\010language\030\007 \001(\t\022\017\n" + + "\007surface\030\010 \001(\t\"U\n\014AgentRequest\022\n\n\002id\030\001 \001" + + "(\t\022\021\n\toperation\030\002 \001(\t\022\020\n\010question\030\003 \001(\t\022" + + "\024\n\014context_json\030\004 \001(\t\"A\n\017AgentToolResult" + + "\022\n\n\002id\030\001 \001(\t\022\023\n\013result_json\030\002 \001(\t\022\r\n\005err" + + "or\030\003 \001(\t\"!\n\013AgentCancel\022\022\n\nrequest_id\030\001 " + + "\001(\t\"\352\001\n\020AgentClientEvent\022)\n\004open\030\001 \001(\0132\031" + + ".message.AgentSessionOpenH\000\022(\n\007request\030\002" + + " \001(\0132\025.message.AgentRequestH\000\022/\n\013tool_re" + + "sult\030\003 \001(\0132\030.message.AgentToolResultH\000\022&" + + "\n\006cancel\030\004 \001(\0132\024.message.AgentCancelH\000\022\037" + + "\n\005close\030\005 \001(\0132\016.message.EmptyH\000B\007\n\005event" + + "\"s\n\nAgentReady\022\017\n\007enabled\030\001 \001(\010\022\016\n\006reaso" + + "n\030\002 \001(\t\022\022\n\nsession_id\030\003 \001(\t\022\017\n\007surface\030\004" + + " \001(\t\022\020\n\010provider\030\005 \001(\t\022\r\n\005model\030\006 \001(\t\"(\n" + + "\020AgentChatMessage\022\024\n\014message_json\030\001 \001(\t\"" + + "A\n\rAgentToolCall\022\n\n\002id\030\001 \001(\t\022\014\n\004name\030\002 \001" + + "(\t\022\026\n\016arguments_json\030\003 \001(\t\"?\n\nAgentError" + + "\022\014\n\004code\030\001 \001(\t\022\017\n\007message\030\002 \001(\t\022\022\n\nreque" + + "st_id\030\003 \001(\t\"\277\001\n\020AgentServerEvent\022$\n\005read" + + "y\030\001 \001(\0132\023.message.AgentReadyH\000\022)\n\004chat\030\002" + + " \001(\0132\031.message.AgentChatMessageH\000\022+\n\ttoo" + + "l_call\030\003 \001(\0132\026.message.AgentToolCallH\000\022$" + + "\n\005error\030\004 \001(\0132\023.message.AgentErrorH\000B\007\n\005" + + "event2\361\017\n\007Service\022C\n\020GetTokenAuthInfo\022\025." + + "message.TokenRequest\032\026.message.TokenResp" + + "onse\"\000\022>\n\nRenewToken\022\025.message.TokenRequ" + + "est\032\027.message.StatusResponse\"\000\022P\n\rCreate" + + "Session\022\035.message.SessionCreateRequest\032\036" + + ".message.SessionCreateResponse\"\000\022L\n\rFini" + + "shSession\022\035.message.SessionFinishRequest" + + "\032\032.message.SessionFinishResp\"\000\022E\n\020Upload" + + "ReplayFile\022\026.message.ReplayRequest\032\027.mes" + + "sage.ReplayResponse\"\000\022D\n\rUploadCommand\022\027" + + ".message.CommandRequest\032\030.message.Comman" + + "dResponse\"\000\022I\n\014DispatchTask\022\034.message.Fi" + + "nishedTaskRequest\032\025.message.TaskResponse" + + "\"\000(\0010\001\022R\n\021ScanRemainReplays\022\034.message.Re" + + "mainReplayRequest\032\035.message.RemainReplay" + + "Response\"\000\022X\n\023CreateCommandTicket\022\036.mess" + + "age.CommandConfirmRequest\032\037.message.Comm" + + "andConfirmResponse\"\000\022f\n\035CheckOrCreateAss" + + "etLoginTicket\022 .message.AssetLoginTicket" + + "Request\032!.message.AssetLoginTicketRespon" + + "se\"\000\022J\n\020CheckTicketState\022\026.message.Ticke" + + "tRequest\032\034.message.TicketStateResponse\"\000" + + "\022A\n\014CancelTicket\022\026.message.TicketRequest" + + "\032\027.message.StatusResponse\"\000\022D\n\rCreateFor" + + "ward\022\027.message.ForwardRequest\032\030.message." + + "ForwardResponse\"\000\022I\n\rDeleteForward\022\035.mes" + + "sage.ForwardDeleteRequest\032\027.message.Stat" + + "usResponse\"\000\022D\n\020GetPublicSetting\022\016.messa" + + "ge.Empty\032\036.message.PublicSettingResponse" + + "\"\000\022?\n\016GetListenPorts\022\016.message.Empty\032\033.m" + + "essage.ListenPortResponse\"\000\022D\n\013GetPortIn" + + "fo\022\030.message.PortInfoRequest\032\031.message.P" + + "ortInfoResponse\"\000\022K\n\021HandlePortFailure\022\033" + + ".message.PortFailureRequest\032\027.message.St" + + "atusResponse\"\000\022F\n\022CheckUserByCookies\022\027.m" + + "essage.CookiesRequest\032\025.message.UserResp" + + "onse\"\000\022[\n\031RecordSessionLifecycleLog\022#.me" + + "ssage.SessionLifecycleLogRequest\032\027.messa" + + "ge.StatusResponse\"\000\022n\n\027FaceRecognitionCa" + + "llback\022\'.message.FaceRecognitionCallback" + + "Request\032(.message.FaceRecognitionCallbac" + + "kResponse\"\000\022b\n\023FaceMonitorCallback\022#.mes" + + "sage.FaceMonitorCallbackRequest\032$.messag" + + "e.FaceMonitorCallbackResponse\"\000\022V\n\017JoinF" + + "aceMonitor\022\037.message.JoinFaceMonitorRequ" + + "est\032 .message.JoinFaceMonitorResponse\"\000\022" + + "B\n\016GetAccountChat\022\016.message.Empty\032\036.mess" + + "age.AccountDetailResponse\"\000\0228\n\007CallAPI\022\024" + + ".message.HTTPRequest\032\025.message.HTTPRespo" + + "nse\"\000\022J\n\014AgentSession\022\031.message.AgentCli" + + "entEvent\032\031.message.AgentServerEvent\"\000(\0010" + + "\001B \n\023org.jumpserver.wispZ\t/protobufb\006pro" + + "to3" }; descriptor = com.google.protobuf.Descriptors.FileDescriptor .internalBuildGeneratedFileFrom(descriptorData, @@ -37178,6 +47269,66 @@ public org.jumpserver.wisp.ServiceOuterClass.HTTPResponse getDefaultInstanceForT com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_message_HTTPResponse_descriptor, new java.lang.String[] { "Status", "Body", }); + internal_static_message_AgentSessionOpen_descriptor = + getDescriptor().getMessageTypes().get(48); + internal_static_message_AgentSessionOpen_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentSessionOpen_descriptor, + new java.lang.String[] { "SessionId", "UserId", "OrganizationId", "AssetId", "AccountId", "Protocol", "Language", "Surface", }); + internal_static_message_AgentRequest_descriptor = + getDescriptor().getMessageTypes().get(49); + internal_static_message_AgentRequest_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentRequest_descriptor, + new java.lang.String[] { "Id", "Operation", "Question", "ContextJson", }); + internal_static_message_AgentToolResult_descriptor = + getDescriptor().getMessageTypes().get(50); + internal_static_message_AgentToolResult_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentToolResult_descriptor, + new java.lang.String[] { "Id", "ResultJson", "Error", }); + internal_static_message_AgentCancel_descriptor = + getDescriptor().getMessageTypes().get(51); + internal_static_message_AgentCancel_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentCancel_descriptor, + new java.lang.String[] { "RequestId", }); + internal_static_message_AgentClientEvent_descriptor = + getDescriptor().getMessageTypes().get(52); + internal_static_message_AgentClientEvent_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentClientEvent_descriptor, + new java.lang.String[] { "Open", "Request", "ToolResult", "Cancel", "Close", "Event", }); + internal_static_message_AgentReady_descriptor = + getDescriptor().getMessageTypes().get(53); + internal_static_message_AgentReady_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentReady_descriptor, + new java.lang.String[] { "Enabled", "Reason", "SessionId", "Surface", "Provider", "Model", }); + internal_static_message_AgentChatMessage_descriptor = + getDescriptor().getMessageTypes().get(54); + internal_static_message_AgentChatMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentChatMessage_descriptor, + new java.lang.String[] { "MessageJson", }); + internal_static_message_AgentToolCall_descriptor = + getDescriptor().getMessageTypes().get(55); + internal_static_message_AgentToolCall_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentToolCall_descriptor, + new java.lang.String[] { "Id", "Name", "ArgumentsJson", }); + internal_static_message_AgentError_descriptor = + getDescriptor().getMessageTypes().get(56); + internal_static_message_AgentError_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentError_descriptor, + new java.lang.String[] { "Code", "Message", "RequestId", }); + internal_static_message_AgentServerEvent_descriptor = + getDescriptor().getMessageTypes().get(57); + internal_static_message_AgentServerEvent_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_message_AgentServerEvent_descriptor, + new java.lang.String[] { "Ready", "Chat", "ToolCall", "Error", "Event", }); descriptor.resolveAllFeaturesImmutable(); org.jumpserver.wisp.Common.getDescriptor(); com.google.protobuf.StructProto.getDescriptor(); diff --git a/config/application-dev.yml.example b/config/application-dev.yml.example new file mode 100644 index 0000000..1cda5cd --- /dev/null +++ b/config/application-dev.yml.example @@ -0,0 +1,9 @@ +mock: + enable: true + mysql: + db-type: mysql + host: host.docker.internal + port: 3306 + user: root + password: your_password + db: test diff --git a/docker-compose.wisp-dev.yml b/docker-compose.wisp-dev.yml new file mode 100644 index 0000000..9ae360d --- /dev/null +++ b/docker-compose.wisp-dev.yml @@ -0,0 +1,40 @@ +services: + chen-backend: + # Development runs Wisp as a sibling service. Start Java directly so the + # released Wisp binary bundled in the Chen image is not used. + command: + - java + - -Dfile.encoding=utf-8 + - -XX:+ExitOnOutOfMemoryError + - -jar + - /opt/chen/chen.jar + - --spring.config.additional-location=optional:file:/opt/chen/config/ + - --spring.profiles.active=dev + environment: + EXECUTE_PROGRAM: "" + GRPC_CLIENT_WISP_ADDRESS: static://wisp:9090 + depends_on: + wisp: + # Arbitrary Wisp images do not necessarily provide a healthcheck. + condition: service_started + cpus: ${CHEN_CPUS:-2.0} + mem_limit: ${CHEN_MEMORY_LIMIT:-2g} + + wisp: + image: ${WISP_IMAGE:-ghcr.io/jumpserver/wisp:latest} + pull_policy: ${WISP_PULL_POLICY:-missing} + container_name: chen-wisp-dev + environment: + COMPONENT_NAME: chen + CORE_HOST: ${CORE_HOST:-http://host.docker.internal:8080} + BOOTSTRAP_TOKEN: ${BOOTSTRAP_TOKEN:-PleaseChangeMe} + LOG_LEVEL: ${LOG_LEVEL:-INFO} + WORK_DIR: /opt/wisp + EXECUTE_PROGRAM: "" + volumes: + - ./data:/opt/wisp/data + expose: + - "9090" + restart: unless-stopped + cpus: ${WISP_CPUS:-1.0} + mem_limit: ${WISP_MEMORY_LIMIT:-512m} diff --git a/docker-compose.wisp-source.yml b/docker-compose.wisp-source.yml new file mode 100644 index 0000000..ea450ee --- /dev/null +++ b/docker-compose.wisp-source.yml @@ -0,0 +1,20 @@ +services: + chen-backend: + depends_on: + wisp: + condition: service_healthy + + wisp: + # This layer replaces the image-only Wisp service with an image built from + # a local directory or any other BuildKit context (for example, a Git URL). + image: ${WISP_SOURCE_IMAGE:-chen-wisp-dev:local} + pull_policy: build + build: + context: . + dockerfile: Dockerfile.wisp-dev + additional_contexts: + wisp-source: ${WISP_SOURCE:-../wisp} + args: + GO_VERSION: ${WISP_GO_VERSION:-1.25} + WISP_BUILD_PROCS: ${WISP_BUILD_PROCS:-2} + WISP_BUILD_MEMORY: ${WISP_BUILD_MEMORY:-1GiB} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6e9aad0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + chen-backend: + build: + context: . + dockerfile: Dockerfile + container_name: chen-backend-dev + volumes: + - ./config:/opt/chen/config + - ./data:/opt/chen/data + ports: + - "8082:8082" + environment: + LOG_LEVEL: INFO + CORE_HOST: ${CORE_HOST:-http://host.docker.internal:8080} + BOOTSTRAP_TOKEN: ${BOOTSTRAP_TOKEN:-PleaseChangeMe} + EXECUTE_PROGRAM: >- + java -Dfile.encoding=utf-8 -XX:+ExitOnOutOfMemoryError + -jar /opt/chen/chen.jar + --spring.config.additional-location=optional:file:/opt/chen/config/ + --spring.profiles.active=dev + restart: unless-stopped + +volumes: + chen-data: diff --git a/entrypoint.sh b/entrypoint.sh index 722db90..68ca079 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -12,7 +12,8 @@ export GIN_MODE=release export WORK_DIR=/opt/chen export COMPONENT_NAME=chen export WISP_TRACE_PROCESS=1 -export EXECUTE_PROGRAM="java -Dfile.encoding=utf-8 -XX:+ExitOnOutOfMemoryError -jar /opt/chen/chen.jar --mock.enable=false" +: ${EXECUTE_PROGRAM:="java -Dfile.encoding=utf-8 -XX:+ExitOnOutOfMemoryError -jar /opt/chen/chen.jar --mock.enable=false"} +export EXECUTE_PROGRAM : ${LOG_LEVEL:='ERROR'} diff --git a/frontend/src/components/Controller/index.vue b/frontend/src/components/Controller/index.vue index d23c174..bb6c590 100644 --- a/frontend/src/components/Controller/index.vue +++ b/frontend/src/components/Controller/index.vue @@ -39,6 +39,7 @@ export default { dialogVisible: false, ws: null, dialogOptions: { + id: null, title: '', showClose: false, width: '30%', @@ -164,7 +165,10 @@ export default { }, onDialogEvent(event) { - this.sendPacket('dialog_event', event) + this.sendPacket('dialog_event', { + dialogId: this.dialogOptions.id, + event + }) }, sendPacket(type, data) { const msg = { type: type, data: data } diff --git a/frontend/src/components/Main/Explore/DataView/DataView.vue b/frontend/src/components/Main/Explore/DataView/DataView.vue index dd4c27c..e3139fa 100644 --- a/frontend/src/components/Main/Explore/DataView/DataView.vue +++ b/frontend/src/components/Main/Explore/DataView/DataView.vue @@ -8,10 +8,14 @@ > - + @@ -27,6 +31,7 @@ import RightMenu from '@/components/Main/Explore/DataView/RightMenu.vue' import { SpecialCharacters, GeneralInsertSQL, GeneralUpdateSQL } from './const' import ResultGrid from '@/components/Main/Explore/DataView/ResultGrid.vue' +import { DataViewRequestState } from './requestState' export default { name: 'DataView', @@ -55,12 +60,32 @@ export default { toolBarItems: { type: Object, default: () => ({}) + }, + editable: { + type: Boolean, + default: false + }, + rowEditActionsEnabled: { + type: Boolean, + default: false + }, + previewBeforeSave: { + type: Boolean, + default: false } }, data() { return { rowData: [], colDefs: [], + dirtyCells: {}, + insertRows: [], + deletedRows: {}, + requestState: new DataViewRequestState(), + nextInsertRowId: 1, + // Set when a save invalidated the connection. Save/preview remain blocked until fresh data + // is loaded through the replacement connection. + refreshRequiredBeforeSave: false, exportDataDialogVisible: false, state: { @@ -75,6 +100,7 @@ export default { type: 'button', icon: 'iconfont icon-chen-first_page', onClick: this.onFirstPage, + disabled: () => this.requestBusy, hidden: () => { return !this.state.paged } @@ -83,6 +109,7 @@ export default { type: 'button', icon: 'iconfont icon-chen-icon_paging_left', onClick: this.onPrevPage, + disabled: () => this.requestBusy, hidden: () => { return !this.state.paged } @@ -121,8 +148,9 @@ export default { } ], onCommand: (command) => { - this.$emit('action', { action: 'change_limit', data: command }) + this.startDataRequest({ action: 'change_limit', data: command }) }, + disabled: () => this.requestBusy, customDisplayContent: () => { let content = '' if (this.isStatePaged) { @@ -136,6 +164,7 @@ export default { type: 'button', icon: 'iconfont icon-chen-icon_paging_right', onClick: this.onNextPage, + disabled: () => this.requestBusy, hidden: () => { return !this.state.paged } @@ -144,6 +173,7 @@ export default { type: 'button', icon: 'iconfont icon-chen-last-page', onClick: this.onLastPage, + disabled: () => this.requestBusy, hidden: () => { return !this.state.paged } @@ -152,7 +182,56 @@ export default { split: true, type: 'button', icon: 'iconfont icon-chen-reload1', - onClick: this.onRefresh + onClick: this.onRefresh, + disabled: () => this.requestBusy + }, + addRow: { + split: true, + type: 'button', + icon: 'iconfont icon-chen-plus', + name: () => 'Add', + hidden: () => { + return !this.resultEditable || !this.rowEditActionsEnabled + }, + disabled: () => this.requestBusy, + onClick: this.onAddRow + }, + deleteRow: { + type: 'button', + icon: 'iconfont icon-chen-minus', + name: () => 'Delete', + hidden: () => { + return !this.resultEditable || !this.rowEditActionsEnabled + }, + disabled: () => { + return this.requestBusy || !this.currentRow + }, + onClick: this.onDeleteRows + }, + saveChanges: { + split: true, + type: 'button', + icon: 'iconfont icon-chen-save', + name: () => 'Save', + hidden: () => { + return !this.resultEditable + }, + disabled: () => { + return this.requestBusy || !this.hasDirty() + }, + onClick: this.onSaveChanges + }, + cancelChanges: { + type: 'button', + icon: 'el-icon-close', + name: () => 'Cancel', + hidden: () => { + return !this.resultEditable + }, + disabled: () => { + return this.requestBusy || !this.hasDirty() + }, + onClick: this.onCancelChanges }, export: { split: true, @@ -188,15 +267,32 @@ export default { } }, computed: { + dirtyVersion() { + return this.requestState.dirtyVersion + }, + requestBusy() { + return !!this.requestState.activeRequest + }, + resultEditable() { + const fields = this.data && Array.isArray(this.data.fields) ? this.data.fields : [] + return this.editable && + this.data && + this.data.editable === true && + fields.some((field) => field && field.editable === true) + }, isStatePaged() { return this.state.paged }, iToolBarItems() { return Object.assign(this.defaultToolBarItems, this.toolBarItems) + }, + toolbarKey() { + return `toolbar-${this.dirtyVersion}-${this.requestState.requestSequence}-${this.requestBusy}` } }, watch: { data() { + this.resetDataSelection() if (!this.init) { this.initTable() } else { @@ -208,6 +304,9 @@ export default { this.stateSubject.subscribe((state) => { if (state.title === this.meta.title) { this.state = state + if (!state.loading) { + this.finishDataRequestWithoutResult() + } } }) }, @@ -216,12 +315,26 @@ export default { return this.state }, reloadTable() { - this.rowData = this.data.data + const rows = this.data && this.data.data ? this.data.data.map((row) => this.normalizeRowData(row)) : [] + rows.forEach((row) => { + if (this.isExistingRowDeleted(row)) { + row.__chenDeleted = true + } + }) + this.rowData = rows.concat(this.insertRows.map((row) => row.data)) }, initTable() { const headers = this.data.fields.map((item) => { return { - field: item.name + field: item.name, + fieldMeta: item, + editable: (params) => this.isCellEditable(params, item), + valueParser: (params) => this.parseCellValue(params.newValue, item), + cellClassRules: { + 'chen-dirty-cell': (params) => this.isDirtyCell(params), + 'chen-insert-row': (params) => this.isInsertRow(params.data), + 'chen-delete-row': (params) => this.isDeleteRow(params.data) + } } }) @@ -230,6 +343,545 @@ export default { this.reloadTable() this.init = true }, + getPrimaryKeyField() { + if (!this.data || !this.data.fields) { + return null + } + return this.data.fields.find((field) => field.primaryKey === true || field.isPrimaryKey === true) + }, + getValueIsNull(value) { + return value === null || value === undefined + }, + valuesEqual(left, right) { + return left === right + }, + buildDirtyKey(pkValue, sourceColumn) { + return `${JSON.stringify(pkValue)}::${sourceColumn}` + }, + buildDeleteKey(pkValue) { + return JSON.stringify(pkValue) + }, + buildInsertKey(id, sourceColumn) { + return `insert:${id}::${sourceColumn}` + }, + hasDirty() { + return Object.keys(this.dirtyCells).length > 0 || + this.insertRows.length > 0 || + Object.keys(this.deletedRows).length > 0 + }, + clearDirty() { + this.dirtyCells = {} + this.insertRows = [] + this.deletedRows = {} + this.requestState.markDirty() + this.resetDataSelection() + this.reloadTable() + this.refreshDirtyCells() + }, + setObjectValue(targetName, key, value) { + this[targetName] = { + ...this[targetName], + [key]: value + } + }, + deleteObjectValue(targetName, key) { + const nextValue = { ...this[targetName] } + delete nextValue[key] + this[targetName] = nextValue + }, + isDirtyCell(params) { + if (!params || !params.data || !params.colDef || !params.colDef.fieldMeta) { + return false + } + if (this.isInsertRow(params.data)) { + const sourceColumn = params.colDef.fieldMeta.sourceColumn + return !!(sourceColumn && params.data.__chenValues && params.data.__chenValues[sourceColumn]) + } + const primaryKeyField = this.getPrimaryKeyField() + const sourceColumn = params.colDef.fieldMeta.sourceColumn + if (!primaryKeyField || !sourceColumn) { + return false + } + const pkValue = params.data[primaryKeyField.name] + if (this.getValueIsNull(pkValue)) { + return false + } + return !!this.dirtyCells[this.buildDirtyKey(pkValue, sourceColumn)] + }, + isInsertRow(row) { + return !!(row && row.__chenInsertId) + }, + isDeleteRow(row) { + return !!(row && row.__chenDeleted) + }, + isExistingRowDeleted(row) { + const primaryKeyField = this.getPrimaryKeyField() + if (!row || !primaryKeyField) { + return false + } + const pkValue = row[primaryKeyField.name] + return !!this.deletedRows[this.buildDeleteKey(pkValue)] + }, + isCellEditable(params, fieldMeta) { + if (this.requestBusy || !this.resultEditable || !fieldMeta || !fieldMeta.sourceColumn) { + return false + } + const row = params ? params.data : null + if (this.isDeleteRow(row)) { + return false + } + if (this.isInsertRow(row)) { + return fieldMeta.insertable === true + } + return fieldMeta.editable === true + }, + refreshDirtyCells() { + const grid = this.$refs.resultGrid + if (grid && grid.gridApi && typeof grid.gridApi.refreshCells === 'function') { + grid.gridApi.refreshCells({ force: true }) + } + }, + onCellValueChanged(params) { + if (this.requestBusy || !this.resultEditable || !params || !params.colDef || !params.colDef.fieldMeta || !params.data) { + return + } + + const fieldMeta = params.colDef.fieldMeta + if (!this.isCellEditable(params, fieldMeta)) { + return + } + if (this.isInsertRow(params.data)) { + this.onInsertCellValueChanged(params, fieldMeta) + return + } + + const primaryKeyField = this.getPrimaryKeyField() + if (!primaryKeyField || !primaryKeyField.sourceColumn || !fieldMeta.sourceColumn) { + return + } + + const pkValue = params.data[primaryKeyField.name] + if (this.getValueIsNull(pkValue)) { + this.$message.warning('Primary key value is empty, cannot edit this row') + return + } + + const sourceColumn = fieldMeta.sourceColumn + const key = this.buildDirtyKey(pkValue, sourceColumn) + const fallbackOldValue = Object.prototype.hasOwnProperty.call(params, 'oldValue') + ? params.oldValue + : params.data[params.colDef.field] + const oldValue = this.dirtyCells[key] + ? this.dirtyCells[key].oldValue + : this.normalizeCellValue(fallbackOldValue, fieldMeta) + const oldValueIsNull = this.dirtyCells[key] + ? this.dirtyCells[key].oldValueIsNull + : this.getValueIsNull(fallbackOldValue) + const newValue = this.normalizeCellValue(params.newValue, fieldMeta) + const newValueIsNull = this.getValueIsNull(newValue) + + if (oldValueIsNull === newValueIsNull && this.valuesEqual(oldValue, newValue)) { + this.deleteObjectValue('dirtyCells', key) + this.requestState.markDirty() + this.refreshDirtyCells() + return + } + + this.setObjectValue('dirtyCells', key, { + pkColumn: primaryKeyField.sourceColumn, + pkValue, + pkValueIsNull: this.getValueIsNull(pkValue), + sourceColumn, + oldValue, + oldValueIsNull, + newValue, + newValueIsNull + }) + this.requestState.markDirty() + this.refreshDirtyCells() + }, + onInsertCellValueChanged(params, fieldMeta) { + const sourceColumn = fieldMeta.sourceColumn + const row = this.insertRows.find((item) => item.id === params.data.__chenInsertId) + if (!row || !sourceColumn) { + return + } + if (params.newValue === undefined) { + this.$delete(row.values, sourceColumn) + this.$delete(row.data.__chenValues, sourceColumn) + } else { + const newValue = this.normalizeCellValue(params.newValue, fieldMeta) + this.$set(row.values, sourceColumn, { + value: newValue, + valueIsNull: this.getValueIsNull(newValue) + }) + this.$set(row.data.__chenValues, sourceColumn, true) + } + this.requestState.markDirty() + this.refreshDirtyCells() + }, + normalizeRowData(row) { + const normalized = { ...row } + const fields = this.data && Array.isArray(this.data.fields) ? this.data.fields : [] + fields.forEach((field) => { + if (field && Object.prototype.hasOwnProperty.call(normalized, field.name)) { + normalized[field.name] = this.normalizeCellValue(normalized[field.name], field) + } + }) + return normalized + }, + parseCellValue(value, fieldMeta) { + return this.normalizeCellValue(value, fieldMeta) + }, + normalizeCellValue(value, fieldMeta) { + if (value === null || value === undefined || !fieldMeta) { + return value + } + const type = this.normalizeFieldType(fieldMeta.type) + if (this.isDateType(type)) { + return this.normalizeDateValue(value) + } + if (this.isTimeType(type)) { + return this.normalizeTimeValue(value) + } + if (this.isTimestampType(type)) { + return this.normalizeTimestampValue(value, this.isOffsetTimestampType(type)) + } + return value + }, + normalizeFieldType(type) { + if (!type) { + return '' + } + let normalized = String(type).trim().toLowerCase() + let changed = true + while (changed) { + changed = false + if (normalized.startsWith('nullable(') && normalized.endsWith(')')) { + normalized = normalized.substring('nullable('.length, normalized.length - 1).trim() + changed = true + } + if (normalized.startsWith('lowcardinality(') && normalized.endsWith(')')) { + normalized = normalized.substring('lowcardinality('.length, normalized.length - 1).trim() + changed = true + } + } + const bracketIndex = normalized.indexOf('(') + if (bracketIndex > -1) { + normalized = normalized.substring(0, bracketIndex).trim() + } + return normalized + }, + isDateType(type) { + return type === 'date' + }, + isTimeType(type) { + return type === 'time' || type === 'time without time zone' + }, + isTimestampType(type) { + return this.isOffsetTimestampType(type) || + ['timestamp', 'timestamp without time zone', 'datetime', 'datetime2', 'smalldatetime'].includes(type) + }, + isOffsetTimestampType(type) { + return ['timestamptz', 'timestamp with time zone', 'datetimeoffset'].includes(type) + }, + normalizeDateValue(value) { + if (value instanceof Date) { + return this.formatDateParts(value.getFullYear(), value.getMonth() + 1, value.getDate()) + } + const text = String(value).trim() + const match = text.match(/^(\d{4}-\d{2}-\d{2})/) + return match ? match[1] : value + }, + normalizeTimeValue(value) { + if (value instanceof Date) { + return this.formatTimeParts(value.getHours(), value.getMinutes(), value.getSeconds()) + } + const text = String(value).trim() + const localized = text.match(/^(\d{1,2}):(\d{2}):(\d{2})(\.\d{1,9})?\s*(上午|下午|AM|PM)$/i) + if (localized) { + let hour = Number(localized[1]) + const meridiem = localized[5].toUpperCase() + if ((meridiem === '下午' || meridiem === 'PM') && hour < 12) { + hour += 12 + } + if ((meridiem === '上午' || meridiem === 'AM') && hour === 12) { + hour = 0 + } + return `${this.pad2(hour)}:${localized[2]}:${localized[3]}${localized[4] || ''}` + } + const canonical = text.match(/^(\d{1,2}):(\d{2}):(\d{2})(\.\d{1,9})?$/) + if (canonical) { + return `${this.pad2(Number(canonical[1]))}:${canonical[2]}:${canonical[3]}${canonical[4] || ''}` + } + const isoTime = text.match(/[T\s](\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?)/) + return isoTime ? isoTime[1] : value + }, + normalizeTimestampValue(value, keepOffset) { + if (value instanceof Date) { + return `${this.formatDateParts(value.getFullYear(), value.getMonth() + 1, value.getDate())} ${this.formatTimeParts(value.getHours(), value.getMinutes(), value.getSeconds())}` + } + const text = String(value).trim() + if (keepOffset) { + return text + } + const match = text.match(/^(\d{4}-\d{2}-\d{2})[T\s](\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?)/) + return match ? `${match[1]} ${match[2]}` : value + }, + formatDateParts(year, month, day) { + return `${year}-${this.pad2(month)}-${this.pad2(day)}` + }, + formatTimeParts(hour, minute, second) { + return `${this.pad2(hour)}:${this.pad2(minute)}:${this.pad2(second)}` + }, + pad2(value) { + return String(value).padStart(2, '0') + }, + buildSaveChangesPayload() { + return { + schema: this.resolveSaveSchema(), + table: this.resolveSaveTable(), + changes: Object.values(this.dirtyCells), + insertRows: this.insertRows + .filter((row) => Object.keys(row.values).length > 0) + .map((row) => ({ values: row.values })), + deleteRows: Object.values(this.deletedRows) + } + }, + resolveSaveSchema() { + if (this.meta && Object.prototype.hasOwnProperty.call(this.meta, 'schema')) { + return this.meta.schema + } + const field = this.getEditableSourceField() + return field ? field.sourceSchema : undefined + }, + resolveSaveTable() { + if (this.meta && Object.prototype.hasOwnProperty.call(this.meta, 'table')) { + return this.meta.table + } + const field = this.getEditableSourceField() + return field ? field.sourceTable : undefined + }, + getEditableSourceField() { + if (!this.data || !this.data.fields) { + return null + } + return this.data.fields.find((field) => field && field.editable === true && field.sourceTable && field.sourceColumn) + }, + onSaveChanges() { + if (this.refreshRequiredBeforeSave) { + this.$message.warning('The connection was reset. Refresh before saving again; transaction and session state were lost.') + return + } + if (this.requestBusy || !this.hasDirty()) { + return + } + + this.stopGridEditing() + const kind = this.previewBeforeSave ? 'preview' : 'save' + const request = this.requestState.begin(kind, this.buildSaveChangesPayload()) + if (!request) { + return + } + const action = { + action: this.previewBeforeSave ? 'save_changes_preview' : 'save_changes', + dataView: this.meta.title, + data: request.payload, + clientRequestSequence: request.sequence + } + + this.$emit('action', action) + }, + handleSaveChangesPreviewResult(result) { + const request = this.requestState.activeRequest + if (!request || !this.requestState.isCurrent(request.sequence, 'preview')) { + return false + } + if (!result || !result.success) { + this.requestState.finish(request.sequence, 'preview') + const reason = result && result.reason ? result.reason : 'Preview failed' + const index = result && result.failedChangeIndex !== undefined && result.failedChangeIndex !== null + ? `, failedChangeIndex=${result.failedChangeIndex}` + : '' + this.$message.error(`${reason}${index}`) + return true + } + if (!this.requestState.hasCurrentDirtyVersion(request.sequence)) { + this.requestState.finish(request.sequence, 'preview') + this.$message.warning('The data changed after preview started. Please preview again.') + return true + } + const updateCount = result.updateCount || 0 + const insertCount = result.insertCount || 0 + const deleteCount = result.deleteCount || 0 + const confirmingRequest = this.requestState.setKind(request.sequence, 'preview', 'confirm') + this.$confirm( + `Preview: ${updateCount} updates, ${insertCount} inserts, ${deleteCount} deletes. Continue?`, + 'Save changes', + { + confirmButtonText: 'Save', + cancelButtonText: 'Cancel', + type: 'warning' + } + ).then(() => { + if (!this.requestState.hasCurrentDirtyVersion(confirmingRequest.sequence)) { + this.requestState.finish(confirmingRequest.sequence, 'confirm') + this.$message.warning('The data changed after preview. Please preview again.') + return + } + const saveRequest = this.requestState.transition(confirmingRequest.sequence, 'confirm', 'save') + if (!saveRequest) { + return + } + this.$emit('action', { + action: 'save_changes', + dataView: this.meta.title, + data: saveRequest.payload, + clientRequestSequence: saveRequest.sequence + }) + }).catch(() => { + this.requestState.finish(confirmingRequest.sequence, 'confirm') + }) + return true + }, + handleSaveChangesResult(result) { + const request = this.requestState.activeRequest + if (!request || !this.requestState.isCurrent(request.sequence, 'save')) { + return false + } + if (result && result.connectionInvalidated && !result.success) { + this.requestState.finish(request.sequence, 'save') + this.refreshRequiredBeforeSave = true + const reason = result.reason || 'Save failed' + const detail = reason === 'SAVE_CHANGES_COMMIT_OUTCOME_UNKNOWN' + ? 'The save commit outcome is unknown' + : reason + this.$message.warning(`${detail}. The connection was reset; transaction and session state were lost. Refresh to verify the database state before retrying.`) + return true + } + if (!result || !result.success) { + this.requestState.finish(request.sequence, 'save') + const reason = result && result.reason ? result.reason : 'Save failed' + const index = result && result.failedChangeIndex !== undefined && result.failedChangeIndex !== null + ? `, failedChangeIndex=${result.failedChangeIndex}` + : '' + this.$message.error(`${reason}${index}`) + return true + } + if (result.connectionInvalidated) { + this.refreshRequiredBeforeSave = true + } + if (!this.requestState.hasCurrentDirtyVersion(request.sequence)) { + this.requestState.finish(request.sequence, 'save') + const message = result.connectionInvalidated + ? 'Save succeeded and the connection was reset. Newer local changes were kept; refresh before saving again because transaction and session state were lost.' + : 'Save succeeded, but newer local changes were kept. Refresh was skipped.' + this.$message.warning(message) + return true + } + this.requestState.finish(request.sequence, 'save') + this.clearDirty() + if (result.connectionInvalidated) { + const auditDetail = result.auditSucceeded === false + ? ' Audit recording also failed; do not retry the save.' + : '' + this.$message.warning(`Save succeeded, but the connection was reset. Transaction and session state were lost; refreshing with a new connection.${auditDetail}`) + } else if (result.auditSucceeded === false) { + const message = result.databaseCommitted + ? 'Save committed, but audit recording failed. Do not retry the save.' + : 'Save applied to the current transaction, but audit recording failed. Do not retry the save.' + this.$message.warning(message) + } else { + this.$message.success('Save succeeded') + } + this.startDataRequest({ action: 'refresh' }) + return true + }, + onCancelChanges() { + if (this.requestBusy) { + return + } + this.clearDirty() + this.onRefresh() + }, + onAddRow() { + if (this.requestBusy) { + return + } + const id = this.nextInsertRowId++ + const data = { + __chenInsertId: id, + __chenValues: {} + } + this.insertRows.push({ + id, + data, + values: {} + }) + this.reloadTable() + this.requestState.markDirty() + this.refreshDirtyCells() + }, + onDeleteRows() { + if (this.requestBusy) { + return + } + const rows = this.getRowsForDelete() + if (rows.length === 0) { + return + } + rows.forEach((row) => this.markRowDeleted(row)) + this.reloadTable() + this.requestState.markDirty() + this.refreshDirtyCells() + }, + getRowsForDelete() { + const grid = this.$refs.resultGrid + if (grid && typeof grid.getRangeRowData === 'function') { + const rows = grid.getRangeRowData() + if (rows.length > 0) { + return rows + } + } + return this.currentRow ? [this.currentRow] : [] + }, + markRowDeleted(row) { + if (!row) { + return + } + if (this.isInsertRow(row)) { + this.insertRows = this.insertRows.filter((item) => item.id !== row.__chenInsertId) + if (this.currentRow === row) { + this.currentRow = null + } + return + } + const primaryKeyField = this.getPrimaryKeyField() + if (!primaryKeyField || !primaryKeyField.sourceColumn) { + this.$message.warning('Primary key is missing, cannot delete this row') + return + } + const pkValue = row[primaryKeyField.name] + if (this.getValueIsNull(pkValue)) { + this.$message.warning('Primary key value is empty, cannot delete this row') + return + } + this.clearRowUpdates(pkValue) + this.setObjectValue('deletedRows', this.buildDeleteKey(pkValue), { + pkColumn: primaryKeyField.sourceColumn, + pkValue, + pkValueIsNull: false + }) + }, + clearRowUpdates(pkValue) { + const nextDirtyCells = { ...this.dirtyCells } + Object.keys(nextDirtyCells).forEach((key) => { + if (key.startsWith(`${JSON.stringify(pkValue)}::`)) { + delete nextDirtyCells[key] + } + }) + this.dirtyCells = nextDirtyCells + }, fallbackWriteClipboardText(text, originalError) { const textarea = document.createElement('textarea') textarea.value = text @@ -252,19 +904,19 @@ export default { } }, onNextPage() { - this.$emit('action', { action: 'next_page' }) + this.startDataRequest({ action: 'next_page' }) }, onPrevPage() { - this.$emit('action', { action: 'prev_page' }) + this.startDataRequest({ action: 'prev_page' }) }, onFirstPage() { - this.$emit('action', { action: 'first_page' }) + this.startDataRequest({ action: 'first_page' }) }, onLastPage() { - this.$emit('action', { action: 'last_page' }) + this.startDataRequest({ action: 'last_page' }) }, onRefresh() { - this.$emit('action', { action: 'refresh' }) + this.startDataRequest({ action: 'refresh' }) }, onExport() { this.exportDataDialogVisible = true @@ -273,13 +925,75 @@ export default { this.exportDataDialogVisible = false this.$emit('action', { action: 'export', data: scope }) }, + onCellClicked(params) { + if (this.requestBusy) { + return + } + this.currentRow = params ? params.data : null + }, showContextMenu(params) { + if (this.requestBusy) { + return + } this.currentRow = params.data this.$refs.rightMenu.show(params.event) }, preventDefaultContextMenu(event) { event.preventDefault() }, + startDataRequest(action) { + if (this.requestBusy) { + return false + } + this.stopGridEditing() + const request = this.requestState.begin('data') + if (!request) { + return false + } + this.$emit('action', { + ...action, + clientRequestSequence: request.sequence + }) + return true + }, + acceptDataResponse() { + const request = this.requestState.activeRequest + if (!request) { + return true + } + if (!this.requestState.finish(request.sequence, 'data')) { + return false + } + this.refreshRequiredBeforeSave = false + this.resetDataSelection() + return true + }, + cancelClientRequest(requestSequence) { + const request = this.requestState.activeRequest + if (!request || request.sequence !== requestSequence) { + return false + } + return this.requestState.finish(request.sequence, request.kind) + }, + finishDataRequestWithoutResult() { + const request = this.requestState.activeRequest + if (request && request.kind === 'data') { + this.requestState.finish(request.sequence, 'data') + } + }, + stopGridEditing() { + const grid = this.$refs.resultGrid + if (grid && grid.gridApi && typeof grid.gridApi.stopEditing === 'function') { + grid.gridApi.stopEditing() + } + }, + resetDataSelection() { + this.currentRow = null + const grid = this.$refs.resultGrid + if (grid && typeof grid.clearSelection === 'function') { + grid.clearSelection() + } + }, wrap(str, specChar) { const result = str ? str.trim() : '' return `${specChar}${result}${specChar}` @@ -344,4 +1058,18 @@ export default { padding-bottom: 26px; background: #2B2B2B; } + +.data-view ::v-deep .chen-dirty-cell { + background-color: rgba(3, 157, 0, 0.28) !important; +} + +.data-view ::v-deep .chen-insert-row { + background-color: rgba(47, 101, 202, 0.24) !important; +} + +.data-view ::v-deep .chen-delete-row { + background-color: rgba(190, 66, 66, 0.26) !important; + color: #b8b8b8; + text-decoration: line-through; +} diff --git a/frontend/src/components/Main/Explore/DataView/ResultGrid.vue b/frontend/src/components/Main/Explore/DataView/ResultGrid.vue index fe095a9..f524a47 100644 --- a/frontend/src/components/Main/Explore/DataView/ResultGrid.vue +++ b/frontend/src/components/Main/Explore/DataView/ResultGrid.vue @@ -7,6 +7,7 @@ :default-col-def="defaultColDef" :grid-options="gridOptions" @grid-ready="onGridReady" + @cell-value-changed="onCellValueChanged" @cell-mouse-down="onCellMouseDown" @cell-mouse-over="onCellMouseOver" @cell-clicked="onCellClicked" @@ -31,6 +32,10 @@ export default { columnDefs: { type: Array, default: () => [] + }, + editable: { + type: Boolean, + default: false } }, data() { @@ -63,7 +68,12 @@ export default { }, watch: { rowData() { - this.clearRangeSelection() + this.clearSelection() + }, + editable(value) { + if (!value && this.gridApi && typeof this.gridApi.stopEditing === 'function') { + this.gridApi.stopEditing() + } } }, mounted() { @@ -81,6 +91,9 @@ export default { onCellContextMenu(params) { this.$emit('cell-context-menu', params) }, + onCellValueChanged(params) { + this.$emit('cell-value-changed', params) + }, getCellRef(params) { if ( !params || @@ -136,6 +149,7 @@ export default { document.removeEventListener('mouseup', this.onDocumentMouseUp) }, onCellClicked(params) { + this.$emit('cell-clicked', params) const cell = this.getCellRef(params) if (!cell) { return @@ -246,6 +260,31 @@ export default { } this.refreshRangeCells() }, + clearSelection() { + this.clearRangeSelection() + if (this.gridApi && typeof this.gridApi.clearFocusedCell === 'function') { + this.gridApi.clearFocusedCell() + } + if (this.gridApi && typeof this.gridApi.deselectAll === 'function') { + this.gridApi.deselectAll() + } + }, + getRangeRowData() { + const bounds = this.getRangeBounds() + if (!bounds || !this.gridApi) { + return [] + } + const rows = [] + const seen = new Set() + for (let rowIndex = bounds.minRow; rowIndex <= bounds.maxRow; rowIndex++) { + const rowNode = this.gridApi.getDisplayedRowAtIndex(rowIndex) + if (rowNode && rowNode.data && !seen.has(rowNode.data)) { + seen.add(rowNode.data) + rows.push(rowNode.data) + } + } + return rows + }, onDocumentKeyDown(event) { if ((!event.ctrlKey && !event.metaKey) || event.key.toLowerCase() !== 'c') { return diff --git a/frontend/src/components/Main/Explore/DataView/index.vue b/frontend/src/components/Main/Explore/DataView/index.vue index c3ad4c6..976186f 100644 --- a/frontend/src/components/Main/Explore/DataView/index.vue +++ b/frontend/src/components/Main/Explore/DataView/index.vue @@ -1,33 +1,36 @@