Skip to content

Commit fc4e2e3

Browse files
committed
For #6848: use Scala code to send emails instead of XPL/XSL
1 parent 6d9c60f commit fc4e2e3

File tree

8 files changed

+704
-195
lines changed

8 files changed

+704
-195
lines changed

build.sbt

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ val CoreLibraryDependencies = Seq(
137137
"com.google.code.gson" % "gson" % GsonVersionForTink,
138138
"com.google.protobuf" % "protobuf-java" % ProtobufJavaVersionForTink,
139139
"bsf" % "bsf" % "2.4.0" % Test,
140+
"com.icegreen" % "greenmail" % "2.1.3" % Test,
140141
"org.apache.commons" % "commons-exec" % "1.3" % Test,
141142
"org.apache.commons" % "commons-dbcp2" % "2.9.0" % Test,
142143
"com.google.guava" % "guava" % "30.0-jre" % Test,

form-runner/jvm/src/main/scala/org/orbeon/oxf/fr/FormRunnerEmailBackend.scala

+55-52
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@ import org.orbeon.oxf.fr.FormRunnerCommon.frc
88
import org.orbeon.oxf.fr.email.EmailMetadata.HeaderName.Custom
99
import org.orbeon.oxf.fr.email.EmailMetadata.{TemplateMatch, TemplateValue}
1010
import org.orbeon.oxf.fr.email.{Attachment, EmailContent, EmailMetadata, EmailMetadataParsing}
11-
import org.orbeon.oxf.fr.persistence.api.PersistenceApi
1211
import org.orbeon.oxf.fr.process.RenderedFormat
1312
import org.orbeon.oxf.fr.s3.{S3, S3Config}
14-
import org.orbeon.oxf.http.HttpMethod
1513
import org.orbeon.oxf.util.StringUtils.*
1614
import org.orbeon.oxf.util.{CoreCrossPlatformSupportTrait, IndentedLogger, TryUtils, XPathCache}
1715
import org.orbeon.oxf.xforms.action.XFormsAPI.inScopeContainingDocument
1816
import org.orbeon.oxf.xforms.function.XFormsFunction
1917
import org.orbeon.saxon.om
20-
import org.orbeon.saxon.om.{NodeInfo, SequenceIterator}
18+
import org.orbeon.saxon.om.{Item, NodeInfo, SequenceIterator}
2119
import org.orbeon.saxon.value.{BooleanValue, StringValue}
2220
import org.orbeon.scaxon.Implicits.*
2321
import org.orbeon.scaxon.SimplePath.*
@@ -32,7 +30,7 @@ import scala.util.Try
3230

3331
trait FormRunnerEmailBackend {
3432

35-
// Only used from `email-form.xsl` and not needed offline.
33+
// Only used from `email-form.xsl`/EmailContent and not needed offline.
3634
//@XPathFunction
3735
def emailAttachmentFilename(
3836
data : NodeInfo,
@@ -81,7 +79,7 @@ trait FormRunnerEmailBackend {
8179
val emailTemplateOpt = emailTemplateElemOpt.map(EmailMetadataParsing.parseCurrentTemplate(_, formDefinition))
8280
val templateValues = emailTemplateOpt.toList.flatMap(_.headers.filter(_._1.entryName == headerName).map(_._2))
8381

84-
values(formDefinition, formData, templateValues)
82+
evaluatedTemplateValues(formDefinition, formData, templateValues)
8583
}
8684

8785
//@XPathFunction
@@ -93,44 +91,65 @@ trait FormRunnerEmailBackend {
9391
val emailTemplateOpt = emailTemplateElemOpt.map(EmailMetadataParsing.parseCurrentTemplate(_, formDefinition))
9492
val templateValues = emailTemplateOpt.toList.flatMap(_.controlsToAttach)
9593

96-
values(formDefinition, formData, templateValues)
94+
evaluatedTemplateValues(formDefinition, formData, templateValues)
9795
}
9896

99-
def values(
100-
formDefinition : NodeInfo,
101-
formData : NodeInfo,
102-
templateValues : List[TemplateValue]
103-
): SequenceIterator = {
97+
def evaluatedTemplateValues(
98+
formDefinition: NodeInfo,
99+
formData : NodeInfo,
100+
templateValues: List[TemplateValue]
101+
): SequenceIterator =
102+
templateValues.flatMap(evaluatedTemplateValue(formDefinition, formData, _))
103+
104+
def evaluatedTemplateValue(
105+
formDefinition: NodeInfo,
106+
formData : NodeInfo,
107+
templateValue : TemplateValue
108+
): List[Item] =
109+
templateValue match {
110+
case TemplateValue.Control(controlName, sectionOpt) =>
111+
controlValue(formDefinition, formData, controlName, sectionOpt)
112+
113+
case TemplateValue.Expression(expression) =>
114+
expressionValue(formDefinition, expression)
115+
116+
case TemplateValue.Text(text) =>
117+
List(StringValue.makeStringValue(text))
118+
}
119+
120+
def controlValue(
121+
formDefinition: NodeInfo,
122+
formData : NodeInfo,
123+
controlName : String,
124+
sectionOpt : Option[String]
125+
): List[NodeInfo] = {
104126

105127
val formDefinitionCtx = new InDocFormRunnerDocContext(formDefinition)
106128

107-
templateValues.flatMap {
108-
// Control not in section template
109-
case TemplateValue.Control(controlName, None) =>
129+
sectionOpt match {
130+
case None =>
131+
// Control not in section template
110132
frc.searchControlsTopLevelOnly(
111133
data = Some(formData),
112134
predicate = FormRunnerCommon.frc.getControlName(_) == controlName)(
113135
ctx = formDefinitionCtx
114-
).flatMap(_.holders).flatten
136+
).toList.flatMap(_.holders).flatten
115137

116-
// Control in section template
117-
case TemplateValue.Control(controlName, Some(sectionTemplate)) =>
138+
case Some(section) =>
139+
// Control in section template
118140
frc.searchControlsUnderSectionTemplates(
119141
head = formDefinition.rootElement.child("*:head").head,
120142
data = Some(formData),
121143
controlPredicate = FormRunnerCommon.frc.getControlName(_) == controlName,
122-
sectionPredicate = frc.getControlNameOpt(_).contains(sectionTemplate))(
144+
sectionPredicate = frc.getControlNameOpt(_).contains(section))(
123145
ctx = formDefinitionCtx
124-
).flatMap(_.holders).flatten
125-
126-
case TemplateValue.Expression(expression) =>
127-
evaluatedExpressionAsStrings(formDefinition, expressionWithProcessedVarReferences(formDefinition, expression))
128-
129-
case TemplateValue.Text(text) =>
130-
List(StringValue.makeStringValue(text))
146+
).toList.flatMap(_.holders).flatten
131147
}
132148
}
133149

150+
def expressionValue(formDefinition: NodeInfo, expression: String): List[StringValue] =
151+
evaluatedExpressionAsStrings(formDefinition, expressionWithProcessedVarReferences(formDefinition, expression))
152+
134153
private def expressionWithProcessedVarReferences(formDefinition: NodeInfo, expression: String): String = {
135154
val formDefinitionCtx = new InDocFormRunnerDocContext(formDefinition)
136155

@@ -205,6 +224,8 @@ trait FormRunnerEmailBackend {
205224
language : String,
206225
templateNameOpt : Option[String]
207226
)(implicit
227+
logger : IndentedLogger,
228+
coreCrossPlatformSupport: CoreCrossPlatformSupportTrait,
208229
formRunnerParams : FormRunnerParams
209230
): List[EmailContent] = {
210231

@@ -243,42 +264,24 @@ trait FormRunnerEmailBackend {
243264
emailContent : EmailContent,
244265
s3PathPrefix : String
245266
)(implicit
246-
logger : IndentedLogger,
247-
coreCrossPlatformSupport: CoreCrossPlatformSupportTrait,
248-
formRunnerParams : FormRunnerParams,
249267
s3Config : S3Config,
250268
s3Client : S3Client
251269
): Try[Unit] = {
252270
// TODO: store email body and headers as well
253-
TryUtils.sequenceLazily(emailContent.allAttachments)(storeAttachmentToS3(_, s3PathPrefix)).map(_ => ())
271+
TryUtils.sequenceLazily(emailContent.attachments)(storeAttachmentToS3(_, s3PathPrefix)).map(_ => ())
254272
}
255273

256274
private def storeAttachmentToS3(
257-
attachment : Attachment,
258-
s3PathPrefix : String
275+
attachment : Attachment,
276+
s3PathPrefix: String
259277
)(implicit
260-
logger : IndentedLogger,
261-
coreCrossPlatformSupport: CoreCrossPlatformSupportTrait,
262-
formRunnerParams : FormRunnerParams,
263-
s3Config : S3Config,
264-
s3Client : S3Client
278+
s3Config : S3Config,
279+
s3Client : S3Client
265280
): Try[PutObjectResponse] = {
266281

267-
val key = s3PathPrefix + attachment.filename
268-
269-
attachment.data match {
270-
case Left(uri) =>
271-
// Attachment given as URI, stream/download it to S3
272-
val connectionResult = PersistenceApi.connectPersistence(
273-
method = HttpMethod.GET,
274-
pathQuery = uri.toString,
275-
formVersionOpt = Left(FormDefinitionVersion.Specific(formRunnerParams.formVersion)).some
276-
)
277-
278-
S3.write(key, connectionResult.content.stream, connectionResult.content.contentLength, attachment.contentType.some)
279-
case Right(byteArray) =>
280-
// Attachment given as byte array, store it to S3 directly
281-
S3.write(key, byteArray, attachment.contentType.some)
282-
}
282+
val key = s3PathPrefix + attachment.filename
283+
val content = attachment.data.content()
284+
285+
S3.write(key, content.stream, content.contentLength, attachment.contentType.some)
283286
}
284287
}

form-runner/jvm/src/main/scala/org/orbeon/oxf/fr/FormRunnerMetadata.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ object FormRunnerMetadata {
7070
val DeselectedCheckboxString = ""
7171

7272
//@XPathFunction
73-
def findAllControlsWithValues(html: Boolean, controlsToExclude: List[NodeInfo]): String = {
73+
def findAllControlsWithValues(html: Boolean, controlsToExclude: List[NodeInfo]): String =
74+
findAllControlsWithValuesExcludingNamedControls(html, controlsToExclude.map(_.stringValue))
75+
76+
def findAllControlsWithValuesExcludingNamedControls(html: Boolean, controlsToExclude: List[String]): String = {
7477

7578
val collector: ErrorEventCollector = EventCollector.Throw
7679

@@ -86,7 +89,7 @@ object FormRunnerMetadata {
8689
gatherRelevantControls(
8790
doc = XFormsAPI.inScopeContainingDocument,
8891
currentLang = Lang(FormRunnerLang.currentFRLang),
89-
controlNamesToExclude = controlsToExclude.map(_.stringValue).toSet,
92+
controlNamesToExclude = controlsToExclude.toSet,
9093
collector = collector
9194
)
9295

0 commit comments

Comments
 (0)