From 20315a3d153dbbd9e746975379f76a6a77c7669f Mon Sep 17 00:00:00 2001 From: Andrea Minetti Date: Fri, 11 Sep 2026 08:21:34 +0200 Subject: [PATCH 1/5] Migrating to openpdf and java 25 --- project/Settings.scala | 4 +- .../main/scala/ch/wsl/box/rest/Module.scala | 4 +- .../wsl/box/rest/io/pdf/OpenHtmlToPDF.scala | 58 +++++++++---------- .../ch/wsl/box/rest/io/pdf/OpenPDF.scala | 41 +++++++++++++ .../scala/ch/wsl/box/rest/io/pdf/Pdf.scala | 2 +- .../logic/functions/RuntimeFunction.scala | 2 +- .../box/rest/logic/functions/UtilsImpl.scala | 5 +- 7 files changed, 80 insertions(+), 36 deletions(-) create mode 100644 server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenPDF.scala diff --git a/project/Settings.scala b/project/Settings.scala index 99a9ee105..f5f696bf6 100755 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -148,7 +148,9 @@ object Settings { "com.typesafe.akka" %% "akka-http-testkit"% versions.akkaHttp % "test", "com.dimafeng" %% "testcontainers-scala-scalatest" % versions.testcontainersScalaVersion % "test", "ch.wavein" %% "scala-thumbnailer" % "0.8.0", - "com.openhtmltopdf" % "openhtmltopdf-pdfbox" % "1.0.9", +// "com.openhtmltopdf" % "openhtmltopdf-pdfbox" % "1.0.9", + "com.github.librepdf" % "openpdf" % "3.0.5", + "com.github.librepdf" % "openpdf-html" % "3.0.5", "org.jsoup" % "jsoup" % "1.23.1", "com.github.spullara.mustache.java" % "compiler" % "0.9.6", "com.fasterxml.jackson.core" % "jackson-databind" % "2.10.3", diff --git a/server/src/main/scala/ch/wsl/box/rest/Module.scala b/server/src/main/scala/ch/wsl/box/rest/Module.scala index 2faefdd19..5d6af8397 100644 --- a/server/src/main/scala/ch/wsl/box/rest/Module.scala +++ b/server/src/main/scala/ch/wsl/box/rest/Module.scala @@ -17,6 +17,7 @@ import com.softwaremill.session.{InMemoryRefreshTokenStorage, RefreshTokenStorag import scribe.Logging import wvlet.airframe._ +import java.util.concurrent.Executors import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration._ @@ -40,7 +41,8 @@ object DefaultModule extends Module { val injector = newDesign .bind[ExecutionContext].toInstance{ - scala.concurrent.ExecutionContext.global + //val executor = Executors.newFixedThreadPool(16) + ExecutionContext.fromExecutorService(null) } .bind[ActorSystem].toInstance{ ActorSystem() diff --git a/server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenHtmlToPDF.scala b/server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenHtmlToPDF.scala index 0db61b608..43d75149d 100644 --- a/server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenHtmlToPDF.scala +++ b/server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenHtmlToPDF.scala @@ -1,29 +1,29 @@ -package ch.wsl.box.rest.io.pdf - -import java.io.ByteArrayOutputStream - -import akka.stream.scaladsl.{Source, StreamConverters} -import akka.util.ByteString -import com.openhtmltopdf.pdfboxout.PdfRendererBuilder -import org.jsoup.Jsoup -import org.jsoup.helper.W3CDom - -class OpenHtmlToPDF extends Pdf { - override def render(html: String): Array[Byte] = { - val os = new ByteArrayOutputStream(); - val pdfRenderer = new PdfRendererBuilder - val htmlDocument = html5ParseDocument(html) - pdfRenderer.withW3cDocument(htmlDocument, "http://localhost:8080/") - //pdfRenderer.withHtmlContent(html, "/") - pdfRenderer.toStream(os) - pdfRenderer.run() - os.close() - println("rendered finished") - os.toByteArray - } - - private def html5ParseDocument(html:String):org.w3c.dom.Document = { - new W3CDom().fromJsoup(Jsoup.parse(html)) - } - -} +//package ch.wsl.box.rest.io.pdf +// +//import java.io.ByteArrayOutputStream +// +//import akka.stream.scaladsl.{Source, StreamConverters} +//import akka.util.ByteString +//import com.openhtmltopdf.pdfboxout.PdfRendererBuilder +//import org.jsoup.Jsoup +//import org.jsoup.helper.W3CDom +// +//class OpenHtmlToPDF extends Pdf { +// override def render(html: String): Array[Byte] = { +// val os = new ByteArrayOutputStream(); +// val pdfRenderer = new PdfRendererBuilder +// val htmlDocument = html5ParseDocument(html) +// pdfRenderer.withW3cDocument(htmlDocument, "http://localhost:8080/") +// //pdfRenderer.withHtmlContent(html, "/") +// pdfRenderer.toStream(os) +// pdfRenderer.run() +// os.close() +// println("rendered finished") +// os.toByteArray +// } +// +// private def html5ParseDocument(html:String):org.w3c.dom.Document = { +// new W3CDom().fromJsoup(Jsoup.parse(html)) +// } +// +//} diff --git a/server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenPDF.scala b/server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenPDF.scala new file mode 100644 index 000000000..a5f81687e --- /dev/null +++ b/server/src/main/scala/ch/wsl/box/rest/io/pdf/OpenPDF.scala @@ -0,0 +1,41 @@ +package ch.wsl.box.rest.io.pdf + +import org.jsoup.Jsoup +import org.jsoup.helper.W3CDom +import org.openpdf.pdf.ITextRenderer +import org.openpdf.resource.{HtmlParserConfig, HtmlResource} + +import java.io.ByteArrayOutputStream + +class OpenPDF extends Pdf { + + override def render(html: String): Array[Byte] = { + val os = new ByteArrayOutputStream() + +// // Parse with custom configuration +// val config = HtmlParserConfig.builder +// .reportErrors(true) +// .allowSelfClosingTags(true) +// .encoding("UTF-8").build +// val resource = HtmlResource.load(html, config) + + + val doc = Jsoup.parse(html.linesIterator.mkString("")) + println(doc.outerHtml) + val renderer = new ITextRenderer + renderer.setDocument(new W3CDom().fromJsoup(doc)) + //renderer.setDocumentFromString(doc.outerHtml) + renderer.layout + renderer.createPDF(os) + + + os.close() + println("rendered finished") + os.toByteArray + } + + private def html5ParseDocument(html:String):org.w3c.dom.Document = { + new W3CDom().fromJsoup(Jsoup.parse(html)) + } + +} diff --git a/server/src/main/scala/ch/wsl/box/rest/io/pdf/Pdf.scala b/server/src/main/scala/ch/wsl/box/rest/io/pdf/Pdf.scala index 045dc4400..93aa7c6f7 100644 --- a/server/src/main/scala/ch/wsl/box/rest/io/pdf/Pdf.scala +++ b/server/src/main/scala/ch/wsl/box/rest/io/pdf/Pdf.scala @@ -5,7 +5,7 @@ import akka.util.ByteString object Pdf { - private val pdf:Pdf = new OpenHtmlToPDF(); + private val pdf:Pdf = new OpenPDF(); def render(html:String): Array[Byte] = pdf.render(html) } diff --git a/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala b/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala index 0530f80cc..9788f4b05 100644 --- a/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala +++ b/server/src/main/scala/ch/wsl/box/rest/logic/functions/RuntimeFunction.scala @@ -26,7 +26,7 @@ trait RuntimePSQL{ trait RuntimeUtils{ def qrCode(url:String):String - def swissTopoMap(geometry:Option[Json], width:Int, height:Int, padding:Int = 500):String + def swissTopoMap(geometry:Option[Json], width:Int, height:Int, padding:Int = 500)(implicit ec:ExecutionContext):String } case class Context(data:Json,ws:RuntimeWS,psql:RuntimePSQL,utils:RuntimeUtils) diff --git a/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala b/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala index 8be1b4bc6..0f36a0ac7 100644 --- a/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala +++ b/server/src/main/scala/ch/wsl/box/rest/logic/functions/UtilsImpl.scala @@ -15,8 +15,7 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import io.circe.Json import java.util -import scala.concurrent.Await -import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration.DurationInt object UtilsImpl extends RuntimeUtils { @@ -36,7 +35,7 @@ object UtilsImpl extends RuntimeUtils { result } - override def swissTopoMap(geomJS: Option[Json], width:Int, height:Int, padding:Int = 500): String = { + override def swissTopoMap(geomJS: Option[Json], width:Int, height:Int, padding:Int = 500)(implicit ec:ExecutionContext): String = { import Geometry._ val geometry = geomJS.get.as[Geometry].toOption.get val minX = geometry.allCoordinates.map(_.x).min From edfedcad2b72733882deacce8f456f3f82a4f681 Mon Sep 17 00:00:00 2001 From: Andrea Minetti Date: Fri, 11 Sep 2026 09:55:56 +0200 Subject: [PATCH 2/5] Fixed global Execution context --- .../src/main/scala/BaseCodeGenerator.scala | 3 +- .../main/scala/CustomizedCodeGenerator.scala | 10 +++--- .../src/main/scala/FieldAccessGenerator.scala | 5 ++- .../ch/wsl/box/rest/auth/oidc/AuthFlow.scala | 31 +++++++++++-------- .../ch/wsl/box/rest/routes/v1/ApiV1.scala | 4 ++- .../translation/DeepLTranslateService.scala | 1 - 6 files changed, 31 insertions(+), 23 deletions(-) diff --git a/codegen/src/main/scala/BaseCodeGenerator.scala b/codegen/src/main/scala/BaseCodeGenerator.scala index fd67a64f9..672a2556a 100755 --- a/codegen/src/main/scala/BaseCodeGenerator.scala +++ b/codegen/src/main/scala/BaseCodeGenerator.scala @@ -6,7 +6,6 @@ import slick.jdbc.meta.MTable import net.ceedubs.ficus.Ficus._ import scala.concurrent.Await -import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent._ import scala.concurrent.duration._ @@ -14,6 +13,8 @@ case class GeneratorParams(tables:Seq[String],views:Seq[String],excludes:Seq[Str trait BaseCodeGenerator { + implicit def _ex:ExecutionContext + def dbSchema:String def connection:Connection def generatorParams:GeneratorParams diff --git a/codegen/src/main/scala/CustomizedCodeGenerator.scala b/codegen/src/main/scala/CustomizedCodeGenerator.scala index f67231168..5bf29fa6d 100755 --- a/codegen/src/main/scala/CustomizedCodeGenerator.scala +++ b/codegen/src/main/scala/CustomizedCodeGenerator.scala @@ -9,8 +9,7 @@ import scribe.{Level, Logger, Priority} import scribe.filter.{level, packageName, select} import slick.codegen.SourceCodeGenerator -import scala.concurrent.Await -import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration.DurationInt @@ -28,7 +27,8 @@ case class GeneratedFiles( fieldRegistry: FieldAccessGenerator ) -case class CodeGenerator(dbSchema:String,connection:Connection, generatorParams:GeneratorParams) extends BaseCodeGenerator { +case class CodeGenerator(dbSchema:String,connection:Connection, generatorParams:GeneratorParams)(implicit val _ex:ExecutionContext) extends BaseCodeGenerator { + def generatedFiles(): GeneratedFiles = { @@ -72,7 +72,9 @@ object CustomizedCodeGenerator { val connection = new ConnectionConfImpl() - CodeGeneratorWriter.write(connection,params,args(0),"ch.wsl.box.generated") + val ex = ExecutionContext.fromExecutorService(null) + + CodeGeneratorWriter.write(connection,params,args(0),"ch.wsl.box.generated")(ex) } diff --git a/codegen/src/main/scala/FieldAccessGenerator.scala b/codegen/src/main/scala/FieldAccessGenerator.scala index 1fcb8f8e6..de8d47b38 100644 --- a/codegen/src/main/scala/FieldAccessGenerator.scala +++ b/codegen/src/main/scala/FieldAccessGenerator.scala @@ -5,13 +5,12 @@ import ch.wsl.box.jdbc.{Connection, Managed, TypeMapping} import ch.wsl.box.model.shared.JSONFieldTypes import slick.model.Model -import scala.concurrent.Await -import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration.DurationInt -case class FieldAccessGenerator(connection:Connection,tabs:Seq[String], views:Seq[String], model:Model,box_schema:String) extends slick.codegen.SourceCodeGenerator(model) +case class FieldAccessGenerator(connection:Connection,tabs:Seq[String], views:Seq[String], model:Model,box_schema:String)(implicit ex:ExecutionContext) extends slick.codegen.SourceCodeGenerator(model) with BoxSourceCodeGenerator with slick.codegen.OutputHelpers { diff --git a/server/src/main/scala/ch/wsl/box/rest/auth/oidc/AuthFlow.scala b/server/src/main/scala/ch/wsl/box/rest/auth/oidc/AuthFlow.scala index 80d4fb6c9..0b4f11c6c 100644 --- a/server/src/main/scala/ch/wsl/box/rest/auth/oidc/AuthFlow.scala +++ b/server/src/main/scala/ch/wsl/box/rest/auth/oidc/AuthFlow.scala @@ -11,26 +11,28 @@ import io.circe.generic.auto._ import scala.concurrent.{ExecutionContext, Future} -object AuthFlow { +case class OpenIDToken( + access_token:String, + expires_in: Int, + refresh_expires_in: Option[Int], + refresh_token: Option[String], + token_type: String, + session_state: Option[String], + scope: Option[String] + ) + +class AuthFlow(implicit ec:ExecutionContext) { + - case class OpenIDToken( - access_token:String, - expires_in: Int, - refresh_expires_in: Option[Int], - refresh_token: Option[String], - token_type: String, - session_state: Option[String], - scope: Option[String] - ) val backend = DefaultFutureBackend() - private def currentUserFromUserInfo(provider:OIDCConf,userInfo: UserInfo)(implicit ec:ExecutionContext, services:Services):Future[CurrentUser] = { + private def currentUserFromUserInfo(provider:OIDCConf,userInfo: UserInfo)(implicit services:Services):Future[CurrentUser] = { val dbUser = services.config.singleUser match { case true => services.connection.user @@ -64,7 +66,7 @@ object AuthFlow { // "session_state": "57b36158-2ad7-4614-80bf-3aa037ab38fe", // "scope": "email profile" // } - def code(provider:OIDCConf,c:String,code_verifier:Option[String])(implicit ex:ExecutionContext, services: Services):Future[Either[ResponseException[String],CurrentUser]] = { + def code(provider:OIDCConf,c:String,code_verifier:Option[String])(implicit services: Services):Future[Either[ResponseException[String],CurrentUser]] = { def authToken = { @@ -121,7 +123,7 @@ object AuthFlow { } - def code(provider_id:String,c:String,state:String)(implicit ex:ExecutionContext, services: Services):Future[Either[ResponseException[String],CurrentUser]] = { + def code(provider_id:String,c:String,state:String)(implicit services: Services):Future[Either[ResponseException[String],CurrentUser]] = { services.config.openid.find(_.provider_id == provider_id) match { case Some(provider) if provider.jwks.isEmpty => code(provider, c,None) @@ -131,6 +133,9 @@ object AuthFlow { case None => Future.failed(new Exception(s"OIDC Provider $provider_id not found")) } + }.recover{ case t:Throwable => + t.printStackTrace() + throw t } diff --git a/server/src/main/scala/ch/wsl/box/rest/routes/v1/ApiV1.scala b/server/src/main/scala/ch/wsl/box/rest/routes/v1/ApiV1.scala index bad785c09..d73973c25 100644 --- a/server/src/main/scala/ch/wsl/box/rest/routes/v1/ApiV1.scala +++ b/server/src/main/scala/ch/wsl/box/rest/routes/v1/ApiV1.scala @@ -88,6 +88,8 @@ case class ApiV1(appVersion:String)(implicit ec:ExecutionContext, sessionManager } + val authFlow = new AuthFlow() + def sso = pathPrefix("sso") { pathPrefix(Segment) { provider_id => path("challenge") { @@ -99,7 +101,7 @@ case class ApiV1(appVersion:String)(implicit ec:ExecutionContext, sessionManager } ~ pathEnd { parameters("code","state") { case (code,state) => - onComplete(AuthFlow.code(provider_id, code, state)) { + onComplete(authFlow.code(provider_id, code, state)) { case Success(value) => value match { case Left(value) => complete(InternalServerError, s"An error occurred: ${value.getMessage}") case Right(user) => boxSetSessionCookie(BoxSession(user)) { diff --git a/server/src/main/scala/ch/wsl/box/services/translation/DeepLTranslateService.scala b/server/src/main/scala/ch/wsl/box/services/translation/DeepLTranslateService.scala index 2acde2511..6f6feb01d 100644 --- a/server/src/main/scala/ch/wsl/box/services/translation/DeepLTranslateService.scala +++ b/server/src/main/scala/ch/wsl/box/services/translation/DeepLTranslateService.scala @@ -1,6 +1,5 @@ package ch.wsl.box.services.translation -import ch.wsl.box.rest.auth.oidc.AuthFlow.OpenIDToken import ch.wsl.box.services.config.{DeepLConfig, FullConfig} import sttp.client4._ import sttp.client4.circe.asJson From eea14cce44a169c72edcbcd47db22f1fbfa54d7a Mon Sep 17 00:00:00 2001 From: Andrea Minetti Date: Fri, 11 Sep 2026 17:16:57 +0200 Subject: [PATCH 3/5] Healthcheck controls DB connection --- .../src/main/scala/ch/wsl/box/jdbc/Connection.scala | 2 ++ server/src/main/scala/ch/wsl/box/rest/Boot.scala | 4 +++- .../src/main/scala/ch/wsl/box/rest/routes/Root.scala | 10 ++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/codegen/src/main/scala/ch/wsl/box/jdbc/Connection.scala b/codegen/src/main/scala/ch/wsl/box/jdbc/Connection.scala index 18106cd30..2c286ef78 100644 --- a/codegen/src/main/scala/ch/wsl/box/jdbc/Connection.scala +++ b/codegen/src/main/scala/ch/wsl/box/jdbc/Connection.scala @@ -46,6 +46,8 @@ trait Connection extends Logging { def adminDB = dbForUser(adminUser,"box_admin",adminDbConnection) + def checkConnection()(implicit ex:ExecutionContext) = adminDB.run(sql"select 1".as[Int].head).map(_ == 1) + def dbForUser(name: String,app_user:String,db:box.jdbc.PostgresProfile.backend.Database = dbConnection): UserDatabase = new UserDatabase { diff --git a/server/src/main/scala/ch/wsl/box/rest/Boot.scala b/server/src/main/scala/ch/wsl/box/rest/Boot.scala index bb58e5a53..477d1413b 100755 --- a/server/src/main/scala/ch/wsl/box/rest/Boot.scala +++ b/server/src/main/scala/ch/wsl/box/rest/Boot.scala @@ -137,7 +137,9 @@ object Boot extends App { for { res <- server.start() } yield res - }.recover{ case t => t.printStackTrace(); throw t} + }.recover{ case t => t.printStackTrace(); + running = false + throw t} while(running) { Thread.sleep(1000) } Await.result(binding.flatMap(_.unbind()), 20.seconds) } diff --git a/server/src/main/scala/ch/wsl/box/rest/routes/Root.scala b/server/src/main/scala/ch/wsl/box/rest/routes/Root.scala index 3edbac947..cea3f0263 100755 --- a/server/src/main/scala/ch/wsl/box/rest/routes/Root.scala +++ b/server/src/main/scala/ch/wsl/box/rest/routes/Root.scala @@ -27,7 +27,8 @@ import ch.wsl.box.rest.{Box, Module} import ch.wsl.box.rest.routes.v1.ApiV1 import ch.wsl.box.services.Services -import scala.util.{Failure, Success} +import scala.concurrent.duration.DurationInt +import scala.util.{Failure, Success, Try} /** * Created by andreaminetti on 15/03/16. @@ -55,9 +56,10 @@ case class Root(appVersion:String,uiVersion:String,akkaConf:Config, origins:Seq[ def status = path("status") { get { - complete( - HttpResponse(entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`,"RUNNING")) - ) + Try(Await.result(services.connection.checkConnection(),2.seconds)) match { + case Success(true) => complete(HttpResponse(entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`,"RUNNING"))) + case _ => complete(StatusCodes.ServiceUnavailable, "DB Connection error") + } } } From 0cfda1f0a7808aed7d53ca82244e3d9a7da0c9ef Mon Sep 17 00:00:00 2001 From: Andrea Minetti Date: Fri, 11 Sep 2026 17:38:07 +0200 Subject: [PATCH 4/5] ABAFUNKS-60 do not join external for fulltext on arrays --- .../main/scala/ch/wsl/box/client/views/EntityTableView.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala b/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala index 972063db0..878410c87 100755 --- a/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala +++ b/client/src/main/scala/ch/wsl/box/client/views/EntityTableView.scala @@ -19,6 +19,7 @@ import ch.wsl.box.client.views.elements.Offline import ch.wsl.box.client.views.helpers.{PopupFrame, TableColumnDrag} import ch.wsl.box.model.shared.EntityKind.VIEW import ch.wsl.box.model.shared.GeoJson.Polygon +import ch.wsl.box.model.shared.JSONFieldTypes.{ARRAY_NUMBER, ARRAY_STRING} import ch.wsl.box.model.shared.geo.GeoDataRequest import ch.wsl.box.model.shared._ import ch.wsl.box.shared.utils.JSONUtils.EnhancedJson @@ -444,7 +445,7 @@ case class EntityTablePresenter(model:ModelProperty[EntityTableModel], onSelect: qWithFullText.copy( fields = Some(selectedFields), lookups = Some(metadata.flatMap(_.fields) - .filter(x => selectedFields.contains(x.name)) + .filter(x => selectedFields.contains(x.name) && !Seq(ARRAY_NUMBER,ARRAY_STRING).contains(x.`type`)) .flatMap( x=> x.lookup match { case Some(value) => value match { case r:JSONFieldLookupRemote => Some(r) From 21a006dffdf5f46fb1dbc3f1fe6897987646881c Mon Sep 17 00:00:00 2001 From: Andrea Minetti Date: Mon, 14 Sep 2026 16:37:43 +0200 Subject: [PATCH 5/5] Export Widget improvement --- .../components/widget/child/ExportWidget.scala | 3 +-- .../scala/ch/wsl/box/rest/logic/FormActions.scala | 14 +------------- .../scala/ch/wsl/box/model/shared/JSONField.scala | 14 +++++++++++++- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/ExportWidget.scala b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/ExportWidget.scala index 0ece83e77..23d919840 100644 --- a/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/ExportWidget.scala +++ b/client/src/main/scala/ch/wsl/box/client/views/components/widget/child/ExportWidget.scala @@ -27,8 +27,7 @@ object ExportWidget extends ComponentWidgetFactory { c <- field.child m <- params.children.find(_.objId == c.objId) } yield { - val childFilters = c.mapping.map(m => JSONQueryFilter.WHERE.eq(m.child,params.allData.get.get(m.parent))) - ExportParams(m,m.table,c.childQuery.getOrElse(JSONQuery.empty).filterWith(childFilters:_*)) + ExportParams(m,m.table,c.query(params.allData.get)) } override def killWidget(): Unit = { diff --git a/server/src/main/scala/ch/wsl/box/rest/logic/FormActions.scala b/server/src/main/scala/ch/wsl/box/rest/logic/FormActions.scala index e08241ad8..0dbc74527 100755 --- a/server/src/main/scala/ch/wsl/box/rest/logic/FormActions.scala +++ b/server/src/main/scala/ch/wsl/box/rest/logic/FormActions.scala @@ -390,22 +390,10 @@ case class FormActions(metadata:JSONMetadata, override def updateDiff(diff: JSONDiff):DBIO[Option[Json]] = ??? - private def createQuery(entity:Json, child: Child):JSONQuery = { - val parentFilter = for{ - m <- child.mapping - } yield { - JSONQueryFilter.withValue(m.child,Some(Filter.EQUALS),entity.get(m.parent)) - } - - val filters = parentFilter ++ child.childQuery.toSeq.flatMap(_.filter) - - child.childQuery.getOrElse(JSONQuery.empty).copy(filter=filters.toList.distinct) - } - private def getChild(dataJson:Json, metadata:JSONMetadata, field:JSONField, child:Child):DBIO[Seq[Json]] = { - val query = createQuery(dataJson,child) + val query = child.query(dataJson) FormActions(metadata,registry,metadataFactory,Some(field)).findSimple(query) } diff --git a/shared/src/main/scala/ch/wsl/box/model/shared/JSONField.scala b/shared/src/main/scala/ch/wsl/box/model/shared/JSONField.scala index b16d5675e..c6fd3ea82 100755 --- a/shared/src/main/scala/ch/wsl/box/model/shared/JSONField.scala +++ b/shared/src/main/scala/ch/wsl/box/model/shared/JSONField.scala @@ -167,7 +167,19 @@ case class JSONFieldMapForeign(valueColumn:String,keyColumns:Seq[String],labelCo case class ChildMapping(parent:String,child:String) -case class Child(objId:UUID, key:String, mapping:Seq[ChildMapping], childQuery:Option[JSONQuery], props:Seq[String], hasData:Boolean) +case class Child(objId:UUID, key:String, mapping:Seq[ChildMapping], childQuery:Option[JSONQuery], props:Seq[String], hasData:Boolean) { + def query(parent_data: Json): JSONQuery = { + val parentFilter = for { + m <- mapping + } yield { + JSONQueryFilter.withValue(m.child, Some(Filter.EQUALS), parent_data.get(m.parent)) + } + val filters = parentFilter ++ childQuery.toSeq.flatMap(_.filter) + + childQuery.getOrElse(JSONQuery.empty).copy(filter = filters.toList.distinct) + + } +} object Child{ def apply(objId: UUID, key: String, parent: Seq[String], child: Seq[String], childQuery: Option[JSONQuery], props:String, hasData:Boolean): Child = {