refactor: extract shared Texts, Jsons, and SqlBindings helpers

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
0264408
2026-09-18 11:20:34 +08:00
co-authored by Cursor
parent 1f6e33c14b
commit 0e9bb4bd16
25 changed files with 189 additions and 176 deletions
@@ -10,6 +10,7 @@ import com.jetlumen.ordo.storage.jdbc.dialect.SqlDialect;
import com.jetlumen.ordo.storage.jdbc.dialect.SqlDialects;
import com.jetlumen.ordo.storage.jdbc.mapper.ApprovalStepMapper;
import com.jetlumen.ordo.storage.jdbc.mapper.ApprovalStepMapper.StepRow;
import com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings;
import com.jetlumen.ordo.storage.jdbc.mapper.StepTransitionMapper;
import com.jetlumen.ordo.storage.jdbc.mapper.StepTransitionMapper.TransitionRow;
@@ -345,17 +346,17 @@ public final class JdbcProcessDefinitionRepository implements ProcessDefinitionR
insertStep.setString(5, step.policy().name());
insertStep.setInt(6, stepOrder++);
insertStep.setString(7, step.kind().name());
insertStep.setString(8, step.actionKey());
SqlBindings.setString(insertStep, 8, step.actionKey());
if (step.due() == null) {
insertStep.setString(9, null);
insertStep.setString(10, null);
insertStep.setString(11, null);
insertStep.setString(12, null);
SqlBindings.setString(insertStep, 9, null);
SqlBindings.setString(insertStep, 10, null);
SqlBindings.setString(insertStep, 11, null);
SqlBindings.setString(insertStep, 12, null);
} else {
insertStep.setString(9, step.due().after().toString());
insertStep.setString(10, step.due().then().name());
insertStep.setString(11, step.due().to());
insertStep.setString(12, step.due().action());
SqlBindings.setString(insertStep, 11, step.due().to());
SqlBindings.setString(insertStep, 12, step.due().action());
}
insertStep.executeUpdate();
}
@@ -376,8 +377,8 @@ public final class JdbcProcessDefinitionRepository implements ProcessDefinitionR
insertTransition.setString(1, definition.id());
insertTransition.setInt(2, definition.version());
insertTransition.setString(3, transition.fromStepId());
insertTransition.setString(4, transition.toStepId());
insertTransition.setString(5, transition.when() == null ? null : transition.when().toJson());
SqlBindings.setString(insertTransition, 4, transition.toStepId());
SqlBindings.setString(insertTransition, 5, transition.when() == null ? null : transition.when().toJson());
insertTransition.setInt(6, transition.priority());
insertTransition.executeUpdate();
}
@@ -6,8 +6,11 @@ import com.jetlumen.ordo.api.ActionExecutionStatus;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.getInstant;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.setBlankAsNull;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.setInstant;
/** Maps rows of {@code ordo_action_execution} to {@link ActionExecution} objects and back. */
public final class ActionExecutionMapper {
@@ -20,11 +23,10 @@ public final class ActionExecutionMapper {
statement.setString(3, execution.stepId());
statement.setString(4, execution.actionKey());
statement.setString(5, execution.status().name());
statement.setTimestamp(6, Timestamp.from(execution.startedAt()));
setInstant(statement, 6, execution.startedAt());
}
public static ActionExecution read(ResultSet resultSet) throws SQLException {
Timestamp finishedAt = resultSet.getTimestamp("finished_at");
return new ActionExecution(
resultSet.getString("id"),
resultSet.getString("instance_id"),
@@ -32,19 +34,15 @@ public final class ActionExecutionMapper {
resultSet.getString("action_key"),
ActionExecutionStatus.valueOf(resultSet.getString("status")),
resultSet.getString("error_message"),
resultSet.getTimestamp("started_at").toInstant(),
finishedAt == null ? null : finishedAt.toInstant());
getInstant(resultSet, "started_at"),
getInstant(resultSet, "finished_at"));
}
public static void bindComplete(PreparedStatement statement, String executionId, ActionExecutionStatus status,
String errorMessage, java.time.Instant finishedAt) throws SQLException {
String errorMessage, Instant finishedAt) throws SQLException {
statement.setString(1, status.name());
if (errorMessage == null || errorMessage.isBlank()) {
statement.setNull(2, Types.VARCHAR);
} else {
statement.setString(2, errorMessage);
}
statement.setTimestamp(3, Timestamp.from(finishedAt));
setBlankAsNull(statement, 2, errorMessage);
setInstant(statement, 3, finishedAt);
statement.setString(4, executionId);
}
}
@@ -7,8 +7,10 @@ import com.jetlumen.ordo.api.TaskStatus;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.getInstant;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.setInstant;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.setString;
/** Maps rows of {@code ordo_approval_task} to {@link ApprovalTask} objects and back. */
public final class ApprovalTaskMapper {
@@ -22,27 +24,23 @@ public final class ApprovalTaskMapper {
statement.setString(4, task.name());
statement.setString(5, task.assignee());
statement.setString(6, task.status().name());
statement.setTimestamp(7, Timestamp.from(task.createdAt()));
if (task.dueAt() == null) {
statement.setNull(8, Types.TIMESTAMP);
} else {
statement.setTimestamp(8, Timestamp.from(task.dueAt()));
}
setInstant(statement, 7, task.createdAt());
setInstant(statement, 8, task.dueAt());
}
public static void bindComplete(PreparedStatement statement, ApprovalTask completedTask) throws SQLException {
TaskAction action = completedTask.action();
statement.setString(1, completedTask.status().name());
statement.setTimestamp(2, Timestamp.from(completedTask.completedAt()));
setInstant(statement, 2, completedTask.completedAt());
if (action == null) {
// SKIPPED tasks have no actor - a sibling candidate's decision already settled the step.
statement.setNull(3, Types.VARCHAR);
statement.setNull(4, Types.VARCHAR);
statement.setNull(5, Types.TIMESTAMP);
setString(statement, 3, null);
setString(statement, 4, null);
setInstant(statement, 5, null);
} else {
statement.setString(3, action.actor());
statement.setString(4, action.comment());
statement.setTimestamp(5, Timestamp.from(action.operatedAt()));
setString(statement, 4, action.comment());
setInstant(statement, 5, action.operatedAt());
}
statement.setString(6, completedTask.id());
statement.setString(7, completedTask.assignee());
@@ -50,11 +48,8 @@ public final class ApprovalTaskMapper {
public static ApprovalTask read(ResultSet resultSet) throws SQLException {
String actionActor = resultSet.getString("action_actor");
Timestamp actionAt = resultSet.getTimestamp("action_at");
TaskAction action = actionActor == null ? null
: new TaskAction(actionActor, resultSet.getString("action_comment"), actionAt.toInstant());
Timestamp completedAt = resultSet.getTimestamp("completed_at");
Timestamp dueAt = resultSet.getTimestamp("due_at");
: new TaskAction(actionActor, resultSet.getString("action_comment"), getInstant(resultSet, "action_at"));
return new ApprovalTask(
resultSet.getString("id"),
resultSet.getString("instance_id"),
@@ -62,9 +57,9 @@ public final class ApprovalTaskMapper {
resultSet.getString("task_name"),
resultSet.getString("assignee"),
TaskStatus.valueOf(resultSet.getString("status")),
resultSet.getTimestamp("created_at").toInstant(),
completedAt == null ? null : completedAt.toInstant(),
getInstant(resultSet, "created_at"),
getInstant(resultSet, "completed_at"),
action,
dueAt == null ? null : dueAt.toInstant());
getInstant(resultSet, "due_at"));
}
}
@@ -2,7 +2,7 @@ package com.jetlumen.ordo.storage.jdbc.mapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jetlumen.ordo.api.Jsons;
import com.jetlumen.ordo.api.ProcessContext;
import java.util.Map;
@@ -13,14 +13,12 @@ import java.util.Map;
* booleans, lists and nested maps.
*/
public final class ProcessContextCodec {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private ProcessContextCodec() {
}
public static String encode(ProcessContext context) {
try {
return OBJECT_MAPPER.writeValueAsString(context.variables());
return Jsons.MAPPER.writeValueAsString(context.variables());
} catch (JsonProcessingException e) {
throw new IllegalStateException("failed to serialize process context to JSON", e);
}
@@ -28,7 +26,7 @@ public final class ProcessContextCodec {
public static ProcessContext decode(String json) {
try {
Map<String, Object> variables = OBJECT_MAPPER.readValue(json, new TypeReference<>() {
Map<String, Object> variables = Jsons.MAPPER.readValue(json, new TypeReference<>() {
});
return new ProcessContext(variables);
} catch (JsonProcessingException e) {
@@ -6,8 +6,10 @@ import com.jetlumen.ordo.api.ProcessEventType;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.getInstant;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.setInstant;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.setString;
/** Maps rows of {@code ordo_process_event} to {@link ProcessEvent} objects and back. */
public final class ProcessEventMapper {
@@ -17,12 +19,12 @@ public final class ProcessEventMapper {
public static void bindInsert(PreparedStatement statement, ProcessEvent event) throws SQLException {
statement.setString(1, event.id());
statement.setString(2, event.instanceId());
setNullableString(statement, 3, event.taskId());
setNullableString(statement, 4, event.stepId());
setString(statement, 3, event.taskId());
setString(statement, 4, event.stepId());
statement.setString(5, event.type().name());
setNullableString(statement, 6, event.actor());
setNullableString(statement, 7, event.detail());
statement.setTimestamp(8, Timestamp.from(event.occurredAt()));
setString(statement, 6, event.actor());
setString(statement, 7, event.detail());
setInstant(statement, 8, event.occurredAt());
}
public static ProcessEvent read(ResultSet resultSet) throws SQLException {
@@ -34,14 +36,6 @@ public final class ProcessEventMapper {
ProcessEventType.valueOf(resultSet.getString("event_type")),
resultSet.getString("actor"),
resultSet.getString("detail"),
resultSet.getTimestamp("occurred_at").toInstant());
}
private static void setNullableString(PreparedStatement statement, int index, String value) throws SQLException {
if (value == null) {
statement.setNull(index, Types.VARCHAR);
} else {
statement.setString(index, value);
}
getInstant(resultSet, "occurred_at"));
}
}
@@ -6,7 +6,9 @@ import com.jetlumen.ordo.api.ProcessStatus;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.getInstant;
import static com.jetlumen.ordo.storage.jdbc.mapper.SqlBindings.setInstant;
/** Maps rows of {@code ordo_process_instance} to {@link ProcessInstance} objects and back. */
public final class ProcessInstanceMapper {
@@ -20,27 +22,25 @@ public final class ProcessInstanceMapper {
statement.setString(4, instance.initiator());
statement.setString(5, instance.status().name());
statement.setString(6, ProcessContextCodec.encode(instance.context()));
statement.setTimestamp(7, Timestamp.from(instance.startedAt()));
statement.setTimestamp(8, instance.finishedAt() == null ? null : Timestamp.from(instance.finishedAt()));
setInstant(statement, 7, instance.startedAt());
setInstant(statement, 8, instance.finishedAt());
}
public static void bindUpdate(PreparedStatement statement, ProcessInstance instance) throws SQLException {
statement.setString(1, instance.status().name());
statement.setTimestamp(2, instance.finishedAt() == null ? null : Timestamp.from(instance.finishedAt()));
setInstant(statement, 2, instance.finishedAt());
statement.setString(3, instance.id());
}
public static ProcessInstance read(ResultSet resultSet) throws SQLException {
Timestamp startedAt = resultSet.getTimestamp("started_at");
Timestamp finishedAt = resultSet.getTimestamp("finished_at");
return new ProcessInstance(
resultSet.getString("id"),
resultSet.getString("definition_id"),
resultSet.getInt("definition_version"),
resultSet.getString("initiator"),
ProcessStatus.valueOf(resultSet.getString("status")),
startedAt.toInstant(),
finishedAt == null ? null : finishedAt.toInstant(),
getInstant(resultSet, "started_at"),
getInstant(resultSet, "finished_at"),
ProcessContextCodec.decode(resultSet.getString("context_json")));
}
}
@@ -0,0 +1,43 @@
package com.jetlumen.ordo.storage.jdbc.mapper;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
/** Nullable JDBC parameter and column helpers. */
public final class SqlBindings {
private SqlBindings() {
}
public static void setString(PreparedStatement statement, int index, String value) throws SQLException {
if (value == null) {
statement.setNull(index, Types.VARCHAR);
} else {
statement.setString(index, value);
}
}
public static void setBlankAsNull(PreparedStatement statement, int index, String value) throws SQLException {
if (value == null || value.isBlank()) {
statement.setNull(index, Types.VARCHAR);
} else {
statement.setString(index, value);
}
}
public static void setInstant(PreparedStatement statement, int index, Instant value) throws SQLException {
if (value == null) {
statement.setNull(index, Types.TIMESTAMP);
} else {
statement.setTimestamp(index, Timestamp.from(value));
}
}
public static Instant getInstant(ResultSet resultSet, String column) throws SQLException {
Timestamp timestamp = resultSet.getTimestamp(column);
return timestamp == null ? null : timestamp.toInstant();
}
}
@@ -66,8 +66,8 @@ class JdbcMysqlIntegrationTest {
reachabilityProblem = e.getMessage();
}
Assumptions.assumeTrue(reachabilityProblem == null,
"MySQL not reachable at " + config("ordo.test.mysql.host", "ORDO_TEST_MYSQL_HOST", "localhost")
+ ":" + config("ordo.test.mysql.port", "ORDO_TEST_MYSQL_PORT", "3306")
"MySQL not reachable at " + JdbcTestSupport.config("ordo.test.mysql.host", "ORDO_TEST_MYSQL_HOST", "localhost")
+ ":" + JdbcTestSupport.config("ordo.test.mysql.port", "ORDO_TEST_MYSQL_PORT", "3306")
+ " — skipping MySQL integration tests (" + reachabilityProblem + ")");
try (Connection connection = rootDataSource.getConnection(); Statement statement = connection.createStatement()) {
@@ -238,28 +238,16 @@ class JdbcMysqlIntegrationTest {
private static MysqlDataSource newDataSource(String database) {
MysqlDataSource mysqlDataSource = new MysqlDataSource();
String host = config("ordo.test.mysql.host", "ORDO_TEST_MYSQL_HOST", "localhost");
String port = config("ordo.test.mysql.port", "ORDO_TEST_MYSQL_PORT", "3306");
String host = JdbcTestSupport.config("ordo.test.mysql.host", "ORDO_TEST_MYSQL_HOST", "localhost");
String port = JdbcTestSupport.config("ordo.test.mysql.port", "ORDO_TEST_MYSQL_PORT", "3306");
String databasePath = database.isEmpty() ? "/" : "/" + database;
mysqlDataSource.setUrl("jdbc:mysql://" + host + ":" + port + databasePath
+ "?allowPublicKeyRetrieval=true&sslMode=DISABLED&characterEncoding=utf8");
mysqlDataSource.setUser(config("ordo.test.mysql.user", "ORDO_TEST_MYSQL_USER", "root"));
mysqlDataSource.setPassword(config("ordo.test.mysql.password", "ORDO_TEST_MYSQL_PASSWORD", "root"));
mysqlDataSource.setUser(JdbcTestSupport.config("ordo.test.mysql.user", "ORDO_TEST_MYSQL_USER", "root"));
mysqlDataSource.setPassword(JdbcTestSupport.config("ordo.test.mysql.password", "ORDO_TEST_MYSQL_PASSWORD", "root"));
return mysqlDataSource;
}
private static String config(String property, String env, String defaultValue) {
String fromProperty = System.getProperty(property);
if (fromProperty != null && !fromProperty.isBlank()) {
return fromProperty;
}
String fromEnv = System.getenv(env);
if (fromEnv != null && !fromEnv.isBlank()) {
return fromEnv;
}
return defaultValue;
}
private static void attempt(CountDownLatch start, CountDownLatch done, AtomicInteger wins,
ApprovalTask completed, JdbcApprovalTaskRepository repository) {
try {
@@ -234,29 +234,17 @@ class JdbcPostgresIntegrationTest {
private static PGSimpleDataSource newDataSource(String currentSchema) {
PGSimpleDataSource pgDataSource = new PGSimpleDataSource();
pgDataSource.setServerNames(new String[]{config("ordo.test.pg.host", "ORDO_TEST_PG_HOST", "localhost")});
pgDataSource.setPortNumbers(new int[]{Integer.parseInt(config("ordo.test.pg.port", "ORDO_TEST_PG_PORT", "5432"))});
pgDataSource.setDatabaseName(config("ordo.test.pg.database", "ORDO_TEST_PG_DATABASE", "postgres"));
pgDataSource.setUser(config("ordo.test.pg.user", "ORDO_TEST_PG_USER", "postgres"));
pgDataSource.setPassword(config("ordo.test.pg.password", "ORDO_TEST_PG_PASSWORD", "postgres"));
pgDataSource.setServerNames(new String[]{JdbcTestSupport.config("ordo.test.pg.host", "ORDO_TEST_PG_HOST", "localhost")});
pgDataSource.setPortNumbers(new int[]{Integer.parseInt(JdbcTestSupport.config("ordo.test.pg.port", "ORDO_TEST_PG_PORT", "5432"))});
pgDataSource.setDatabaseName(JdbcTestSupport.config("ordo.test.pg.database", "ORDO_TEST_PG_DATABASE", "postgres"));
pgDataSource.setUser(JdbcTestSupport.config("ordo.test.pg.user", "ORDO_TEST_PG_USER", "postgres"));
pgDataSource.setPassword(JdbcTestSupport.config("ordo.test.pg.password", "ORDO_TEST_PG_PASSWORD", "postgres"));
if (!currentSchema.isEmpty()) {
pgDataSource.setCurrentSchema(currentSchema);
}
return pgDataSource;
}
private static String config(String property, String env, String defaultValue) {
String fromProperty = System.getProperty(property);
if (fromProperty != null && !fromProperty.isBlank()) {
return fromProperty;
}
String fromEnv = System.getenv(env);
if (fromEnv != null && !fromEnv.isBlank()) {
return fromEnv;
}
return defaultValue;
}
private static void attempt(CountDownLatch start, CountDownLatch done, AtomicInteger wins,
ApprovalTask completed, JdbcApprovalTaskRepository repository) {
try {
@@ -30,6 +30,18 @@ final class JdbcTestSupport {
return dataSource;
}
static String config(String property, String env, String defaultValue) {
String fromProperty = System.getProperty(property);
if (fromProperty != null && !fromProperty.isBlank()) {
return fromProperty;
}
String fromEnv = System.getenv(env);
if (fromEnv != null && !fromEnv.isBlank()) {
return fromEnv;
}
return defaultValue;
}
static void applySchema(DataSource dataSource) {
applySchema(dataSource, POSTGRES_BASELINE, POSTGRES_V2);
}