This repository has been archived by the owner on Dec 22, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
RestController.scala
208 lines (177 loc) · 10.5 KB
/
RestController.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package controllers
import javax.inject.Inject
import controllers.conversion._
import controllers.conversion.Converter._
import nl.rabobank.rules.engine.{Context, FactEngine}
import nl.rabobank.rules.facts.Fact
import nl.rabobank.rules.service.dsl.BusinessService
import play.api.data.validation.ValidationError
import play.api.libs.json._
import play.api.mvc.{Action, Controller, Request, Result}
import services.{BusinessServicesService, DerivationsService, GlossariesService, JsonConversionMapsService}
import scala.util.{Failure, Success, Try}
// scalastyle:off public.methods.have.type
class RestController @Inject() (businessServicesService: BusinessServicesService,
derivationsService: DerivationsService,
glossariesService: GlossariesService,
jsonConversionMapsService: JsonConversionMapsService) extends Controller {
val endpoints: Try[JsObject] = businessServicesService.businessServiceNames.map(
businessServiceName => JsObject(Map(("/api/run/group/" + businessServiceName) -> Json.toJson("/api/run/group/information/" + businessServiceName)))
) match {
case Nil => Failure(new IllegalStateException("No endpoints available: it seems no BusinessServices have been defined!"))
case jsObjectList: List[JsObject] => Success(jsObjectList.reduceLeft(_ ++ _))
}
/**
* @return a list of JsObjects where the first value is the endpoint and the second value is the information endpoint for all available BusinessServices
* or an InternalServerError(500) if no BusinessServices have been found as this suggests a configuration error.
*/
def businessservices = Action(
endpoints match {
case f: Failure[JsObject] => InternalServerError(f.exception.getMessage)
case s: Success[JsObject] => Ok(s.value)
}
)
/**
* provides information on verplichteInvoer, uitvoer and optioneleFacts
* @param name: the name of the BusinessService for which
* @return
*/
def information(name: String) = Action {
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => Ok(
JsObject(Map(
"Information for Business Service " + s.value._1 ->
JsObject(Map(
"verplichteInvoer" -> contextToJson(s.value._2.verplichteInvoerFacts.map(f => f -> ("type " + f.valueType)).toMap, jsonConversionMap),
"optioneleInvoer met bijbehorende defaults" -> contextToJson(s.value._2.optioneleInvoerFacts, jsonConversionMap),
"uitvoer" -> contextToJson(s.value._2.uitvoerFacts.map(f => f -> ("type " + f.valueType)).toMap, jsonConversionMap)))
)))
}
}
/**
* Attempts to run the derivations specified by the named BusinessService with the JSON context provided.
* Will provide clear error information on all detected issues. Otherwise will provide the provided inputs and the outputs.
* @param name: the name of the BusinessService whose derivations are meant to be run, currently case sensitive
* @return the provided inputs and the specified outputs, nicely sorted.
*/
def runBusinessService(name: String) = Action(parse.json) {
request =>
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => runBusiness(request, InputsAndOutputsResponseJsObject, s.value._2)
}
}
/**
* Attempts to run the derivations specified by the named BusinessService with the JSON context provided.
* Will provide clear error information on all detected issues. Otherwise will provide the provided context, all intermediary results and the outputs.
* @param name: the name of the BusinessService whose derivations are meant to be run, currently case sensitive
* @return The inputs, intermediary results and outputs, nicely sorted.
*/
def debugBusinessService(name: String) = Action(parse.json) {
request =>
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => runBusiness(request, CompleteResponseJsObject, s.value._2)
}
}
/**
* Attempts to run the derivations specified by the named BusinessService with the JSON context provided.
* Will provide clear error information on all detected issues. Otherwise will provide only the specified uitvoer.
* @param name: the name of the BusinessService whose derivations are meant to be run, currently case sensitive
* @return only the outputs belonging to the BusinessService
*/
def runBusinessServiceOutputsOnly(name: String) = Action(parse.json) {
request =>
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => runBusiness(request, OutputsOnlyResponseJsObject, s.value._2)
}
}
private def findBusinessService(name: String): Try[(String, BusinessService)] = {
val matchedBusinessServices = businessServicesService.businessServices.collect{ case (naam, service) if naam == name => (naam, service)}
matchedBusinessServices match {
case Nil => Failure(
new IllegalArgumentException("No BusinessService matched this name, make sure you have used the proper endpoint definition!" ++ businessServicesService.businessServiceNames.toString)
)
case head :: tail => tail match {
case Nil => Success(head)
case tail: List[(String, BusinessService)] => Failure(
new IllegalStateException("More than one BusinessService matched this name. Suspected mistake in BusinessService specifications.")
)
}
}
}
private def runBusiness(request: Request[JsValue], jsonResponseProvider: ResponseJsObject, businessService: BusinessService): Result = {
val (initialContextFragments: List[JsSuccess[Context]], conversionErrors: List[JsError]) = {
convertToIndividualContext(request.body, businessService.glossaries.foldLeft(Map.empty[String, Fact[Any]])((acc, glossary) => acc ++ glossary.facts), jsonConversionMap)
}
if (conversionErrors != List.empty) BadRequest( processConversionErrors(conversionErrors) )
else processConvertedContextBusinessService(initialContextFragments, jsonResponseProvider, businessService)
}
/**
* Provides a REST endpoint for triggering all derivations in the target project. Any fact definitions available in the target project's glossaries
* can be provided in the JSON request body like so:
* {
* "factOfTypeString": "factText",
* "factOfTypeBedrag": 234,
* "factOfTypeBigDecimal": 234,
* "factOfTypePercentage": 234
* }
*
* @return A JsonObject containing either:
* - A list of JsErrors, containing complete error information concerning failed conversions from json to context (if multiple errors occur, you receive information on all of them)
* - A JsObject containing one JsObject: "facts", which contains the combined information of "input" and "results"
*/
def runAll = Action(parse.json) { request =>
run(request, RunAllResponseJsObject)
}
/**
* As #runAll except:
*
* @return A JsonObject containing either:
* - A list of JsErrors, containing complete error information concerning failed conversions from json to context (if multiple errors occur, you receive information on all of them)
* - A JsObject containing two JsObject: "input" and "results", which contains only the information of "results"
*/
def runAllDebug = Action(parse.json) { request =>
run(request, DebugAllResponseJsObject)
}
/**
* As #runAll except:
*
* @return A JsonObject containing either:
* - A list of JsErrors, containing complete error information concerning failed conversions from json to context (if multiple errors occur, you receive information on all of them)
* - A JsObject containing one JsObject: "results", which contains only the information of "results"
*/
def runAllResultsOnly = Action(parse.json) { request =>
run(request, RunAllResultsOnlyResponseJsObject)
}
val jsonConversionMap: JsonConversionsProvider = jsonConversionMapsService.mergedJsonConversionMap
private def run(request: Request[JsValue], jsonResponseProvider: ResponseJsObject) = {
val (initialContextFragments: List[JsSuccess[Context]], conversionErrors: List[JsError]) =
convertToIndividualContext(request.body, glossariesService.mergedGlossaries, jsonConversionMap)
if (conversionErrors != List.empty) BadRequest( processConversionErrors(conversionErrors) )
else Ok( processConvertedContext(initialContextFragments, Nil, jsonResponseProvider) )
}
private def processConversionErrors(conversionErrors: List[JsError]): JsObject = JsError.toJson(conversionErrors.reduceLeft(_ ++ _))
private def processConvertedContext(initialContextFragments: List[JsSuccess[Context]], uitvoerFacts: List[Fact[Any]], jsonResponse: ResponseJsObject): JsObject = {
val initialContext: Context = initialContextFragments.foldLeft(Map.empty[Fact[Any], Any])((acc, jsSuccess) => acc ++ jsSuccess.get)
val resultContext: Context = RulesRunner.run(initialContext, derivationsService.topLevelDerivations)
jsonResponse.toJson(initialContext = initialContext, uitvoerFacts = uitvoerFacts, resultContext = resultContext, jsonConversionMap)
}
private def processConvertedContextBusinessService(initialContextFragments: List[JsSuccess[Context]], jsonResponse: ResponseJsObject, businessService: BusinessService): Result = {
val initialContext: Context = initialContextFragments.foldLeft(Map.empty[Fact[Any], Any])((acc, jsSuccess) => acc ++ jsSuccess.get)
val resultContext: Try[Context] = businessService.run(initialContext, FactEngine.runNormalDerivations)
resultContext match {
case f: Failure[Context] => BadRequest( JsError.toJson(JsError(ValidationError("Attempt at calculation failed due to validation errors: " + f.exception.getMessage))) )
case s: Success[Context] => Ok(
jsonResponse.toJson(
initialContext = initialContext,
uitvoerFacts = businessService.uitvoerFacts,
resultContext = s.value,
jsonConversionMap
)
)
}
}
}