15
15
// limitations under the License.
16
16
package controllers
17
17
18
-
18
+ import scala . language . postfixOps
19
19
import java .io .IOException
20
20
21
21
import akka .actor .ActorSystem
22
22
import akka .http .scaladsl .Http
23
23
import akka .http .scaladsl .model ._
24
24
import akka .http .scaladsl .marshalling .Marshal
25
25
import akka .http .scaladsl .marshallers .sprayjson .SprayJsonSupport
26
+ import akka .http .scaladsl .unmarshalling .Unmarshal
26
27
import akka .stream .ActorMaterializer
27
28
import akka .util .ByteString
28
29
import javax .inject ._
29
30
import play .api .Configuration
30
31
import play .api .mvc ._
31
32
import utils .CommonHelper
32
- import org .parboiled2 .ParseError
33
- import de .upb .cs .swt .delphi .core .ql .Syntax
33
+ import models .{InternalFeature , QueryRequestBody }
34
+ import org .parboiled2 .{ErrorFormatter , ParseError }
35
+ import de .upb .cs .swt .delphi .core .ql
34
36
35
37
import scala .util .{Failure , Success , Try }
36
- import de .upb .cs .swt .delphi .core .ql
38
+ import de .upb .cs .swt .delphi .core .ql ._
39
+ import play .api .libs .json ._
37
40
38
41
import scala .concurrent .{Await , Future }
39
42
import spray .json .DefaultJsonProtocol
@@ -47,6 +50,7 @@ import scala.concurrent.duration._
47
50
class HomeController @ Inject ()(assets : Assets ,configuration : Configuration , cc : ControllerComponents ) extends AbstractController (cc)
48
51
with SprayJsonSupport with DefaultJsonProtocol {
49
52
53
+ var featureList : List [InternalFeature ] = List [InternalFeature ]()
50
54
/**
51
55
* Create an Action to render an HTML page with a welcome message.
52
56
* The configuration in the `routes` file means that this method
@@ -61,47 +65,87 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
61
65
* @return
62
66
*/
63
67
64
- def query ( query : String ) : Action [AnyContent ] = Action .async {
68
+ def search : Action [AnyContent ] = Action .async {
65
69
implicit request => {
66
-
67
- implicit val system = ActorSystem ()
68
- implicit val ec = system.dispatcher
69
- implicit val materializer = ActorMaterializer ()
70
- implicit val queryFormat = jsonFormat2(Query )
71
-
72
- val parser = new Syntax (query)
73
- val parsingResult : Try [ql.Query ] = parser.QueryRule .run()
74
- parsingResult match {
75
- case Success (_) =>
76
- val baseUri = Uri (CommonHelper .getDelphiServer())
77
- val prettyParam = Map (" pretty" -> " " )
78
- val searchUri = baseUri.withPath(baseUri.path + " /search" ).withQuery(akka.http.scaladsl.model.Uri .Query (prettyParam))
79
- val responseFuture = Marshal (Query (query, CommonHelper .limit)).to[RequestEntity ] flatMap { entity =>
80
- Http ().singleRequest(HttpRequest (uri = searchUri, method = HttpMethods .POST , entity = entity))
81
- }
82
- val response = Await .result(responseFuture, 10 seconds)
83
- val resultFuture : Future [String ] = response match {
84
- case HttpResponse (StatusCodes .OK , headers, entity, _) =>
85
- entity.dataBytes.runFold(ByteString (" " ))(_ ++ _).map { body =>
86
- body.utf8String
87
- }
88
- case resp@ HttpResponse (code, _, _, _) => {
89
- resp.discardEntityBytes()
90
- // Future("")
91
- Future .failed(throw new IOException (code.defaultMessage()))
70
+ val jsonBody : Option [JsValue ] = Some (request.body.asJson.get)
71
+ val jsonEmptyBody = Json .toJson(QueryRequestBody (" " ,Some (0 )))
72
+ val json : JsValue = jsonBody.getOrElse(jsonEmptyBody)
73
+ val query = json.as[QueryRequestBody ]
74
+
75
+ val resultLimit = query.limit.getOrElse(CommonHelper .defaultFetchSize)
76
+ if (resultLimit <= CommonHelper .maxFetchSize) {
77
+ val parser = new Syntax (query.query)
78
+ val parsingResult : Try [ql.Query ] = parser.QueryRule .run()
79
+ parsingResult match {
80
+ case Success (parsedQuery) =>
81
+ val invalidFields = checkParsedQuery(parsedQuery, query.limit)
82
+ if (invalidFields.size > 0 ) {
83
+ // Future.failed(throw new IllegalArgumentException(s"Unknown field name(s) used. (${invalidFields.mkString(",")})"))
84
+ Future .successful(new Status (EXPECTATION_FAILED )(s " Incorrect metric name(s) [ ${invalidFields.mkString(" , " )}] entered " ))
85
+ }
86
+ else {
87
+ val searchResult = executeQuery(query.query, query.limit)
88
+ searchResult
92
89
}
90
+ case Failure (e : ParseError ) => {
91
+ val parserInput = parser.input
92
+ val formatter = new ErrorFormatter ()
93
+ val parseErrorResponse = JsObject (Seq (
94
+ " problem" -> JsString (formatter.formatProblem(e, parserInput)),
95
+ " line" -> JsNumber (e.position.line),
96
+ " column" -> JsNumber (e.position.column),
97
+ " query" -> JsString (formatter.formatErrorLine(e, parserInput)),
98
+ " suggestion" -> JsString (formatter.formatExpected(e))))
99
+ Future .successful(new Status (UNPROCESSABLE_ENTITY )(parseErrorResponse))
93
100
}
94
- val result = Await .result(resultFuture, Duration .Inf )
95
- val queryResponse : Future [Result ] = Future .successful(Ok (result.toString))
96
- queryResponse
101
+ case Failure (_) =>
102
+ Future .successful(new Status (EXPECTATION_FAILED )(" Search query failed" ))
103
+ }
104
+ }
105
+ else {
106
+ Future .successful(new Status (EXPECTATION_FAILED )(s " Query limit exceeded default limit: ${resultLimit}> ${CommonHelper .maxFetchSize}" ))
107
+ }
108
+ }
109
+ }
110
+
111
+ private def checkParsedQuery (parsedQuery : ql.Query , limit : Option [Int ]) : Set [String ] = {
112
+ val fields = collectFieldNames(parsedQuery.expr)
113
+ val publicFieldNames = featureList.map(i => i.name)
114
+ val invalidFields = fields.toSet.filter(f => ! publicFieldNames.contains(f))
115
+ invalidFields
116
+ }
117
+
118
+ private def executeQuery (query : String , limit : Option [Int ]) : Future [Result ] = {
97
119
98
- case Failure (e : ParseError ) =>
99
- val errorResponse : Future [Result ] = Future .successful(new Status (EXPECTATION_FAILED )(parser.formatError(e)))
100
- errorResponse
120
+ implicit val system = ActorSystem ()
121
+ implicit val ec = system.dispatcher
122
+ implicit val materializer = ActorMaterializer ()
123
+ implicit val queryFormat = jsonFormat2(Query )
124
+
125
+ val baseUri = Uri (CommonHelper .getDelphiServer)
126
+ val prettyParam = Map (" pretty" -> " " )
127
+ val searchUri = baseUri.withPath(baseUri.path + " /search" ).withQuery(akka.http.scaladsl.model.Uri .Query (prettyParam))
128
+ val responseFuture = Marshal (Query (query, limit)).to[RequestEntity ] flatMap { entity =>
129
+ Http ().singleRequest(HttpRequest (uri = searchUri, method = HttpMethods .POST , entity = entity))
130
+ }
131
+ val response = Await .result(responseFuture, 10 seconds)
132
+ val resultFuture : Future [String ] = response match {
133
+
134
+ case HttpResponse (StatusCodes .OK , headers, entity, _) =>
135
+ entity.dataBytes.runFold(ByteString (" " ))(_ ++ _).map { body =>
136
+ body.utf8String
137
+ }
138
+ case resp@ HttpResponse (code, _, _, _) => {
139
+ resp.discardEntityBytes()
140
+ Future .failed(throw new IOException (code.defaultMessage()))
101
141
}
102
142
}
143
+ val result = Await .result(resultFuture, Duration .Inf )
144
+ val queryResponse : Future [Result ] = Future .successful(Ok (result.toString))
145
+ queryResponse
103
146
}
104
147
148
+
105
149
/**
106
150
* Get list of features
107
151
*
@@ -114,14 +158,11 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
114
158
implicit val system = ActorSystem ()
115
159
implicit val ec = system.dispatcher
116
160
implicit val materializer = ActorMaterializer ()
161
+ implicit val queryFormat = jsonFormat2(InternalFeature )
117
162
118
163
val featuresUri = CommonHelper .getDelphiServer() + " /features"
119
-
120
164
val responseFuture : Future [HttpResponse ] = Http ().singleRequest(HttpRequest (uri = featuresUri, method = HttpMethods .GET ))
121
-
122
-
123
165
val response = Await .result(responseFuture, 10 seconds)
124
-
125
166
val resultFuture : Future [String ] = response match {
126
167
case HttpResponse (StatusCodes .OK , headers, entity, _) =>
127
168
entity.dataBytes.runFold(ByteString (" " ))(_ ++ _).map { body =>
@@ -132,8 +173,9 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
132
173
Future .failed(throw new IOException (code.defaultMessage()))
133
174
}
134
175
}
135
-
136
176
val result = Await .result(resultFuture, Duration .Inf )
177
+ val featureListFuture = Unmarshal (result).to[List [InternalFeature ]]
178
+ featureList = Await .result(featureListFuture, Duration .Inf )
137
179
val queryResponse : Future [Result ] = Future .successful(Ok (result))
138
180
queryResponse
139
181
}
@@ -150,30 +192,52 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
150
192
implicit val ec = system.dispatcher
151
193
implicit val materializer = ActorMaterializer ()
152
194
153
- val retrieveUri = CommonHelper .getDelphiServer() + " /retrieve/" + elementId
154
- val responseRetrieve : Future [HttpResponse ] = Http ().singleRequest(HttpRequest (uri = retrieveUri, method = HttpMethods .GET ))
195
+ val splitString : Array [String ] = elementId.split(':' )
196
+ if (splitString.length < 2 || splitString.length > 3 ) {
197
+ Future .successful(new Status (EXPECTATION_FAILED )(s " Incorrect maven identifier format " ))
198
+ }
199
+ else {
200
+ val retrieveUri = CommonHelper .getDelphiServer() + " /retrieve/" + elementId
201
+ val responseRetrieve : Future [HttpResponse ] = Http ().singleRequest(HttpRequest (uri = retrieveUri, method = HttpMethods .GET ))
155
202
156
- val response = Await .result(responseRetrieve, 10 seconds)
203
+ val response = Await .result(responseRetrieve, 10 seconds)
157
204
158
- val resultFuture : Future [String ] = response match {
159
- case HttpResponse (StatusCodes .OK , headers, entity, _) =>
160
- entity.dataBytes.runFold(ByteString (" " ))(_ ++ _).map { body =>
161
- body.utf8String
205
+ val resultFuture : Future [String ] = response match {
206
+ case HttpResponse (StatusCodes .OK , headers, entity, _) =>
207
+ entity.dataBytes.runFold(ByteString (" " ))(_ ++ _).map { body =>
208
+ body.utf8String
209
+ }
210
+ case resp@ HttpResponse (code, _, _, _) => {
211
+ resp.discardEntityBytes()
212
+ Future .failed(throw new IOException (code.defaultMessage()))
162
213
}
163
- case resp@ HttpResponse (code, _, _, _) => {
164
- resp.discardEntityBytes()
165
- Future .failed(throw new IOException (code.defaultMessage()))
166
214
}
215
+
216
+ val result = Await .result(resultFuture, Duration .Inf )
217
+ val retrieveResponse : Future [Result ] = Future .successful(Ok (result))
218
+ retrieveResponse
167
219
}
220
+ }
221
+ }
168
222
169
- val result = Await .result(resultFuture, Duration .Inf )
170
- val retrieveResponse : Future [Result ] = Future .successful(Ok (result))
171
- retrieveResponse
223
+ private def collectFieldNames (node : CombinatorialExpr ): Seq [String ] = {
224
+ node match {
225
+ case AndExpr (left, right) => collectFieldNames(left) ++ collectFieldNames(right)
226
+ case OrExpr (left, right) => collectFieldNames(left) ++ collectFieldNames(right)
227
+ case NotExpr (expr) => collectFieldNames(expr)
228
+ case XorExpr (left, right) => collectFieldNames(left) ++ collectFieldNames(right)
229
+ case EqualExpr (field, _) => Seq (field.fieldName)
230
+ case NotEqualExpr (field, _) => Seq (field.fieldName)
231
+ case GreaterThanExpr (field, _) => Seq (field.fieldName)
232
+ case GreaterOrEqualExpr (field, _) => Seq (field.fieldName)
233
+ case LessThanExpr (field, _) => Seq (field.fieldName)
234
+ case LessOrEqualExpr (field, _) => Seq (field.fieldName)
235
+ case LikeExpr (field, _) => Seq (field.fieldName)
236
+ case IsTrueExpr (field) => Seq (field.fieldName)
237
+ case FieldReference (name) => Seq (name)
238
+ case _ => Seq ()
172
239
}
173
240
}
174
241
175
- case class Query (query : String , limit : Option [Int ] = None )
242
+ case class Query (query : String , limit : Option [Int ] = Some ( CommonHelper .defaultFetchSize) )
176
243
}
177
-
178
-
179
-
0 commit comments