From ceae27cca21773a04be7a7ea29fa8db2c7029a0b Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 22 Jun 2026 07:33:51 +0200 Subject: [PATCH 01/12] build: remove redundant flexmark-util-options 0.64.0 dep (fixes #8) flexmark-profile-pegdown:0.40.8 already pulls flexmark-util:0.40.8 which provides com.vladsch.flexmark.util.options.*. The explicit 0.64.0 direct dependency introduced an incompatible second tree of util submodules on the classpath with no corresponding core at that version. --- obp-api/pom.xml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 5f62fa3a73..5e499463da 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -319,12 +319,7 @@ flexmark-profile-pegdown 0.40.8 - - - com.vladsch.flexmark - flexmark-util-options - 0.64.0 - + org.web3j From 2270998c3145b49844fa5740c20f8be0b86be3cd Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 22 Jun 2026 08:55:02 +0200 Subject: [PATCH 02/12] build: replace tools.jackson 3.x yaml dep with com.fasterxml 2.x (#7) --- obp-api/pom.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 5f62fa3a73..b554d8ffff 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -514,11 +514,12 @@ jackson-databind - + - tools.jackson.dataformat + com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 3.0.4 From 7a5f620ff894fada21c420cdcf4b52d535a06a38 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 22 Jun 2026 09:51:14 +0200 Subject: [PATCH 03/12] build: fix stale comment plural after flexmark-util-options removal --- obp-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 5e499463da..cbcaed3716 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -312,7 +312,7 @@ json-smart - + com.vladsch.flexmark From 633ede58e7e5daee73dbafbc567617335b255094 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 22 Jun 2026 15:28:18 +0200 Subject: [PATCH 04/12] chore: replace org.everit.json.schema with networknt json-schema-validator (fixes #10) Migrate JSONFactory1_4_0Test from the unmaintained everit SchemaLoader API to com.networknt json-schema-validator 1.0.87 (already on the classpath). Remove the everit dependency from obp-api/pom.xml. Update stale CVE-override comments in obp-api/pom.xml and pom.xml that referenced everit as the transitive source. --- obp-api/pom.xml | 8 +------- .../scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala | 11 ++++++----- pom.xml | 6 ++---- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 5f62fa3a73..45692375ae 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -20,11 +20,6 @@ obp-commons - - com.github.everit-org.json-schema - org.everit.json.schema - 1.6.1 - @@ -128,8 +123,7 @@ snappy-java 1.1.10.4 - commons-beanutils diff --git a/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala index a0c44cc42f..517c455ab4 100644 --- a/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala +++ b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala @@ -12,8 +12,8 @@ import code.api.v1_2_1.OBPAPI1_2_1 import org.json4s.Extraction.decompose import org.json4s._ import com.openbankproject.commons.util.JsonAliases._ -import org.everit.json.schema.loader.SchemaLoader -import org.json.JSONObject +import com.fasterxml.jackson.databind.ObjectMapper +import com.networknt.schema.{JsonSchemaFactory, SpecVersion} import scala.collection.mutable; @@ -160,9 +160,10 @@ class JSONFactory1_4_0Test extends code.setup.ServerSetup { json <- List(compactRender(decompose(resourceDoc.success_response_body))) jsonSchema <- List(compactRender(resourceDoc.typed_success_response_body)) } yield { - val rawSchema = new JSONObject(jsonSchema) - val schema = SchemaLoader.load(rawSchema) - schema.validate(new JSONObject(json)) + val mapper = new ObjectMapper() + val schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4).getSchema(mapper.readTree(jsonSchema)) + val errors = schema.validate(mapper.readTree(json)) + assert(errors.isEmpty, s"JSON Schema validation failed: ${errors}") } } diff --git a/pom.xml b/pom.xml index 56e6a0e231..cecd1678a7 100644 --- a/pom.xml +++ b/pom.xml @@ -88,15 +88,13 @@ accessors-smart 2.6.0 - + org.json json 20250107 - + commons-validator commons-validator From 6d8a34d01eaef60d4e15dcb0296e4fa3f1a19801 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 22 Jun 2026 15:38:25 +0200 Subject: [PATCH 05/12] build: remove orphaned commons-beanutils pin; hoist ObjectMapper outside loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove commons-beanutils:1.11.0 direct dependency from obp-api/pom.xml — no remaining transitive dep pulls it in after the everit removal, so the CVE override had no effect. Hoist new ObjectMapper() and JsonSchemaFactory.getInstance() above the for-comprehension in JSONFactory1_4_0Test so instances are shared across ~190 resource-doc iterations instead of being reconstructed per iteration. --- obp-api/pom.xml | 7 ------- .../test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala | 5 +++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 45692375ae..0a4b44fdc9 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -123,13 +123,6 @@ snappy-java 1.1.10.4 - - - commons-beanutils - commons-beanutils - 1.11.0 - + + org.apache.avro + avro + 1.11.4 + + org.xerial.snappy diff --git a/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala b/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala index 8dec75f379..d6406bb793 100644 --- a/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala +++ b/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala @@ -5,33 +5,30 @@ import java.io.{ByteArrayOutputStream, InputStream} import com.sksamuel.avro4s._ import scala.concurrent.{ExecutionContext, Future} -import scala.util.Try +import scala.util.Success -/** - * Provides generic serialization/deserialization - */ trait AvroSerializer { - def serialize[T: SchemaFor : ToRecord](event: T)(implicit executionContext: ExecutionContext): String = { + def serialize[T: Encoder](event: T)(implicit executionContext: ExecutionContext): String = { val baos = new ByteArrayOutputStream() - val output = AvroOutputStream.json[T](baos) + val output = AvroOutputStream.json[T].to(baos).build() output.write(event) output.close() - val r = baos.toString("UTF-8") - r + baos.toString("UTF-8") } - def serializeFuture[T: SchemaFor : ToRecord](event: T)(implicit executionContext: ExecutionContext): Future[String] = + def serializeFuture[T: Encoder](event: T)(implicit executionContext: ExecutionContext): Future[String] = Future(serialize(event)) - def deserializeFuture[T >: Null : SchemaFor : FromRecord](data: String)(implicit executionContext: ExecutionContext): Future[Option[T]] = + def deserializeFuture[T >: Null : Decoder](data: String)(implicit executionContext: ExecutionContext): Future[Option[T]] = Future(deserialize[T](data)) - def deserialize[T >: Null : SchemaFor : FromRecord](data: String)(implicit executionContext: ExecutionContext): Option[T] = { - - val input = AvroInputStream.json[T](new StringInputStream(data)) - val result: Try[T] = input.singleEntity - result.toOption + def deserialize[T >: Null : Decoder](data: String)(implicit executionContext: ExecutionContext): Option[T] = { + val schema = implicitly[Decoder[T]].schema + val input = AvroInputStream.json[T].from(new StringInputStream(data)).build(schema) + val result = input.tryIterator.collectFirst { case Success(v) => v } + input.close() + result } class StringInputStream(s: String) extends InputStream { @@ -47,4 +44,4 @@ trait AvroSerializer { r.toInt } } -} \ No newline at end of file +} diff --git a/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala b/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala index 0443a26d75..89c163e8c1 100644 --- a/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala +++ b/obp-api/src/main/scala/code/bankconnectors/akka/AkkaConnector_vDec2018.scala @@ -18,7 +18,6 @@ import com.openbankproject.commons.dto._ import com.openbankproject.commons.model._ import com.openbankproject.commons.model.enums.StrongCustomerAuthenticationStatus.SCAStatus import com.openbankproject.commons.model.enums.{AccountAttributeType, CardAttributeType, ChallengeType, CustomerAttributeType, ProductAttributeType, StrongCustomerAuthentication, TransactionAttributeType, TransactionRequestStatus} -import com.sksamuel.avro4s.SchemaFor import net.liftweb.common.{Box, Full} import com.openbankproject.commons.util.JsonAliases.parse @@ -55,8 +54,8 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit { inboundStatus, inboundAdapterInfoInternal) ), - outboundAvroSchema = Some(parse(SchemaFor[OutBoundGetAdapterInfo]().toString(true))), - inboundAvroSchema = Some(parse(SchemaFor[InBoundGetAdapterInfo]().toString(true))), + outboundAvroSchema = None, + inboundAvroSchema = None, adapterImplementation = Some(AdapterImplementation("- Core", 1)) ) override def getAdapterInfo(callContext: Option[CallContext]): Future[Box[(InboundAdapterInfoInternal, Option[CallContext])]] = { @@ -81,8 +80,8 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit { List(bankCommons) ) ), - outboundAvroSchema = Some(parse(SchemaFor[OutBoundGetBanks]().toString(true))), - inboundAvroSchema = Some(parse(SchemaFor[InBoundGetBanks]().toString(true))), + outboundAvroSchema = None, + inboundAvroSchema = None, adapterImplementation = Some(AdapterImplementation("- Core", 2)) ) @@ -110,8 +109,8 @@ object AkkaConnector_vDec2018 extends Connector with AkkaConnectorActorInit { bankCommons ) ), - outboundAvroSchema = Some(parse(SchemaFor[OutBoundGetBank]().toString(true))), - inboundAvroSchema = Some(parse(SchemaFor[InBoundGetBank]().toString(true))), + outboundAvroSchema = None, + inboundAvroSchema = None, adapterImplementation = Some(AdapterImplementation("- Core", 5)) ) override def getBank(bankId : BankId, callContext: Option[CallContext]): Future[Box[(Bank, Option[CallContext])]] = { diff --git a/pom.xml b/pom.xml index cecd1678a7..92993c243d 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ 2.12.20 1.1.5 1.1.0 - 1.8.2 + 4.1.2 v1.0.4 0.23.30 From abe0f663a23e83f77bc389862d79c68c99f77fd7 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Tue, 23 Jun 2026 11:57:41 +0200 Subject: [PATCH 09/12] fix: correct byte sign-extension in StringInputStream and clarify avro property names - Fix StringInputStream.read(): r.toInt & 0xFF to zero-extend bytes 0x80-0xFF instead of sign-extending; signed Byte.toInt returns -1 for 0xFF which JSON parser interprets as EOF, silently truncating any non-ASCII Avro payload - Fix StringInputStream.getBytes: explicit UTF-8 charset to match the UTF-8 used in baos.toString("UTF-8") on the serialize side - Rename to and introduce in parent pom so both versions are visible and independently bumpable --- obp-api/pom.xml | 4 ++-- .../src/main/scala/code/bankconnectors/AvroSerializer.scala | 4 ++-- pom.xml | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 0c52835986..7a789094c9 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -122,7 +122,7 @@ org.apache.avro avro - 1.11.4 + ${apache.avro.version} @@ -236,7 +236,7 @@ com.sksamuel.avro4s avro4s-core_${scala.version} - ${avro.version} + ${avro4s.version} org.apache.commons diff --git a/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala b/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala index d6406bb793..9d75703790 100644 --- a/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala +++ b/obp-api/src/main/scala/code/bankconnectors/AvroSerializer.scala @@ -32,7 +32,7 @@ trait AvroSerializer { } class StringInputStream(s: String) extends InputStream { - private val bytes = s.getBytes + private val bytes = s.getBytes("UTF-8") private var pos = 0 @@ -41,7 +41,7 @@ trait AvroSerializer { } else { val r = bytes(pos) pos += 1 - r.toInt + r.toInt & 0xFF } } } diff --git a/pom.xml b/pom.xml index 92993c243d..6ecae876c8 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,8 @@ 2.12.20 1.1.5 1.1.0 - 4.1.2 + 4.1.2 + 1.11.4 v1.0.4 0.23.30 From df13d40b822fe290fef42ddc9ae30b09590478b9 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Tue, 23 Jun 2026 12:59:57 +0200 Subject: [PATCH 10/12] chore: remove httpclient direct dep and scope AHC to test (fixes #18) - Remove explicit org.apache.httpcomponents:httpclient:4.5.14 declaration; the artifact was only referenced by an unused import in OBPAPIDynamicEndpoint (import only appeared in a comment). It remains available transitively via elasticsearch-rest-client for Elasticsearch's internal use. - Drop unused import org.apache.http.HttpStatus from OBPAPIDynamicEndpoint (the one call site using it was already commented out). - Move org.asynchttpclient:async-http-client to test; the sole direct usage is OPTIONSTest.scala (test class), not production code. --- obp-api/pom.xml | 6 +----- .../code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 7a789094c9..66bf746e62 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -172,11 +172,6 @@ cglib 3.3.0 - - org.apache.httpcomponents - httpclient - 4.5.14 - org.apache.commons commons-pool2 @@ -412,6 +407,7 @@ org.asynchttpclient async-http-client 2.15.0 + test javax.activation diff --git a/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala b/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala index d3dd7b4700..9713e4cff4 100644 --- a/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala +++ b/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala @@ -33,7 +33,6 @@ import code.api.util.{APIUtil, VersionedOBPApis} import code.util.Helper.MdcLoggable import com.openbankproject.commons.util.{ApiVersion,ApiVersionStatus} import net.liftweb.common.{Box, Full} -import org.apache.http.HttpStatus /* This file defines which endpoints from all the versions are available in v4.0.0 From 4f5a85a36c1cdb01387b0930f3fea6f15d5a3298 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Tue, 23 Jun 2026 13:24:15 +0200 Subject: [PATCH 11/12] chore: remove dead imports from OBPAPIDynamicEndpoint Remove imports that were only used in the commented-out Lift CORS block: - net.liftweb.common.{Box, Full} - code.api.dynamic.endpoint.helper.DynamicEndpoints (unused entirely) - code.api.util.APIUtil (no live usages; keep VersionedOBPApis for extends clause) --- .../code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala b/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala index 9713e4cff4..cf2ce89f8e 100644 --- a/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala +++ b/obp-api/src/main/scala/code/api/dynamic/endpoint/OBPAPIDynamicEndpoint.scala @@ -28,11 +28,9 @@ package code.api.dynamic.endpoint import APIMethodsDynamicEndpoint.ImplementationsDynamicEndpoint import code.api.OBPRestHelper -import code.api.dynamic.endpoint.helper.DynamicEndpoints -import code.api.util.{APIUtil, VersionedOBPApis} +import code.api.util.VersionedOBPApis import code.util.Helper.MdcLoggable import com.openbankproject.commons.util.{ApiVersion,ApiVersionStatus} -import net.liftweb.common.{Box, Full} /* This file defines which endpoints from all the versions are available in v4.0.0 From ea098f43d0aed17b5ce89ee64702c28d9258e694 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Wed, 24 Jun 2026 21:13:37 +0200 Subject: [PATCH 12/12] fix: use runUpdate in ProjectionDualWrite so projection rows commit Dynamic-entity POST handlers run without withBusinessDBTransaction, so the TTL-based request-scope proxy is never set. DoobieUtil.runQuery uses Strategy.void on its fallback transactor, which means INSERT runs on a HikariCP connection (autoCommit=false) and is silently rolled back when the connection returns to the pool. Switch to DoobieUtil.runUpdate, which commits via fallbackUpdateTransactor (Strategy.default) when no proxy is present, and still reuses the request transaction via transactorFromConnection when a proxy is available. This makes obp_exists[parcel_on_chain] and obp_not_exists[parcel_on_chain] return correct results for the OGCR parcel/parcel_on_chain join queries. --- .../entity/projection/ProjectionDualWrite.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/obp-api/src/main/scala/code/api/dynamic/entity/projection/ProjectionDualWrite.scala b/obp-api/src/main/scala/code/api/dynamic/entity/projection/ProjectionDualWrite.scala index d59fb43ab9..b9420802ed 100644 --- a/obp-api/src/main/scala/code/api/dynamic/entity/projection/ProjectionDualWrite.scala +++ b/obp-api/src/main/scala/code/api/dynamic/entity/projection/ProjectionDualWrite.scala @@ -9,13 +9,13 @@ import org.json4s.JsonAST.JObject /** * Keeps a record's projection row in sync on the write path (DE_indexing, Phase 3). Guarded by * `projectionEnabled` and a no-op unless the entity has a `ready` projection — so it changes nothing - * by default. Uses `DoobieUtil.runQuery`, which reuses Lift's request connection, so the projection - * upsert/delete participates in the SAME transaction as the canonical blob write (commit/rollback - * together). Scalar fields only (spatial dual-write is Phase 4). + * by default. Uses `DoobieUtil.runUpdate` so the INSERT is committed even when called outside an + * explicit request-scope transaction (e.g. dynamic-entity POST handlers that don't wrap in + * `withBusinessDBTransaction`). When a request-scope proxy IS present, `runUpdate` still reuses it + * via `transactorFromConnection`. Scalar fields only (spatial dual-write is Phase 4). */ object ProjectionDualWrite extends MdcLoggable { - /** Upsert the record's ready indexed scalar columns into the projection (called after the blob save). */ def onSave(bankId: Option[String], entityName: String, dataId: String, body: JObject): Unit = withReadyScalarFields(bankId, entityName) { (safeTable, fields) => val cols = fields.map { case (f, spec) => @@ -24,13 +24,12 @@ object ProjectionDualWrite extends MdcLoggable { ProjectionDDL.sqlColumnType(spec.fieldType.toString), ProjectionCoerce.toColumnValue(body \ f, spec.fieldType)) } - DoobieUtil.runQuery(ProjectionStore.upsert(safeTable, dataId, cols)) + DoobieUtil.runUpdate(ProjectionStore.upsert(safeTable, dataId, cols)) } - /** Delete the record's projection row (called after the blob delete; FK cascade is a backstop). */ def onDelete(bankId: Option[String], entityName: String, dataId: String): Unit = withReadyScalarFields(bankId, entityName) { (safeTable, _) => - DoobieUtil.runQuery(ProjectionStore.delete(safeTable, dataId)) + DoobieUtil.runUpdate(ProjectionStore.delete(safeTable, dataId)) } private def withReadyScalarFields(bankId: Option[String], entityName: String)