diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala index ed8fefb1fa..517263a544 100644 --- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala +++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala @@ -157,6 +157,7 @@ object Migration extends MdcLoggable { migrateMetricConsentReferenceId(startedBeforeSchemifier) migrateMetricCertificateTrust(startedBeforeSchemifier) dropFastFirehoseAccountsViews(startedBeforeSchemifier) + alterDynamicResourceDocBodyFieldsLength() } /** @@ -511,6 +512,13 @@ object Migration extends MdcLoggable { // Retire the fast-firehose SQL views (firehose -> account directory + ABAC). Runs after the create // migrations above, so a fresh DB creates-then-drops them and an existing DB just drops them. See // MigrationOfDropFastFireHoseViews. + private def alterDynamicResourceDocBodyFieldsLength(): Boolean = { + val name = nameOf(alterDynamicResourceDocBodyFieldsLength) + runOnce(name) { + MigrationOfDynamicResourceDocBodyFieldsLength.alterColumnsType(name) + } + } + private def dropFastFirehoseAccountsViews(startedBeforeSchemifier: Boolean): Boolean = { if(startedBeforeSchemifier == true) { logger.warn(s"Migration.database.dropFastFirehoseAccountsViews(true) cannot be run before Schemifier.") diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfDynamicResourceDocBodyFieldsLength.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfDynamicResourceDocBodyFieldsLength.scala new file mode 100644 index 0000000000..187ab049cf --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfDynamicResourceDocBodyFieldsLength.scala @@ -0,0 +1,64 @@ +package code.api.util.migration + +import code.api.util.APIUtil +import code.api.util.migration.Migration.{DbFunction, saveLog} +import code.dynamicResourceDoc.DynamicResourceDoc +import net.liftweb.common.Full +import net.liftweb.mapper.Schemifier + +object MigrationOfDynamicResourceDocBodyFieldsLength { + + def alterColumnsType(name: String): Boolean = { + DbFunction.tableExists(DynamicResourceDoc) match { + case true => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + var isSuccessful = false + + val executedSql = + DbFunction.maybeWrite(true, Schemifier.infoF _) { + APIUtil.getPropsValue("db.driver") match { + case Full(dbDriver) if dbDriver.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") => + () => + """ + |-- A realistic dynamic-endpoint request/response body example (or full error + |-- response list) routinely exceeds varchar(255) once it has more than a + |-- handful of JSON fields + |ALTER TABLE dynamicresourcedoc ALTER COLUMN examplerequestbody VARCHAR(MAX); + |ALTER TABLE dynamicresourcedoc ALTER COLUMN successresponsebody VARCHAR(MAX); + |ALTER TABLE dynamicresourcedoc ALTER COLUMN errorresponsebodies VARCHAR(MAX); + |""".stripMargin + case _ => + () => + """ + |-- A realistic dynamic-endpoint request/response body example (or full error + |-- response list) routinely exceeds varchar(255) once it has more than a + |-- handful of JSON fields + |ALTER TABLE dynamicresourcedoc ALTER COLUMN examplerequestbody TYPE text; + |ALTER TABLE dynamicresourcedoc ALTER COLUMN successresponsebody TYPE text; + |ALTER TABLE dynamicresourcedoc ALTER COLUMN errorresponsebodies TYPE text; + |""".stripMargin + } + } + + val endDate = System.currentTimeMillis() + val comment: String = + s"""Executed SQL: + |$executedSql + |""".stripMargin + isSuccessful = true + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + + case false => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = false + val endDate = System.currentTimeMillis() + val comment: String = + s"""${DynamicResourceDoc._dbTableNameLC} table does not exist""".stripMargin + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } + } +} diff --git a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala index b70552fe01..b850b84581 100644 --- a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala +++ b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -522,7 +522,13 @@ object JSONFactory1_4_0 extends MdcLoggable{ } def checkFieldOption(jsonBody: Any, rootFields: List[Field]) = { - val types = rootFields.map(f => (f.getName(), f.getType().getCanonicalName().contains("Option"))) + // getCanonicalName() is null for a local/anonymous class (Java reflection spec) -- which is + // exactly what a nested case class declared inside a runtime-compiled DynamicResourceDoc + // method body is, from the JVM's perspective (e.g. an example_request_body with a nested + // object generates a locally-scoped case class for that object's type). A field whose + // declared type is such a class is never itself an Option (Option's own canonical name is + // always present, since scala.Option is a top-level class), so None safely defaults to false. + val types = rootFields.map(f => (f.getName(), Option(f.getType().getCanonicalName()).exists(_.contains("Option")))) (decompose(jsonBody), types) } diff --git a/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDoc.scala b/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDoc.scala index d11c21ae41..a96c164350 100644 --- a/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDoc.scala +++ b/obp-api/src/main/scala/code/dynamicResourceDoc/DynamicResourceDoc.scala @@ -19,9 +19,9 @@ class DynamicResourceDoc extends LongKeyedMapper[DynamicResourceDoc] with IdPK w object RequestUrl extends MappedString(this, 255) object Summary extends MappedString(this, 255) object Description extends MappedString(this, 255) - object ExampleRequestBody extends MappedString(this, 255) - object SuccessResponseBody extends MappedString(this, 255) - object ErrorResponseBodies extends MappedString(this, 255) + object ExampleRequestBody extends MappedText(this) + object SuccessResponseBody extends MappedText(this) + object ErrorResponseBodies extends MappedText(this) object Tags extends MappedString(this, 255) object Roles extends MappedString(this, 255) object MethodBody extends MappedText(this)