Skip to content

Commit 64760ce

Browse files
authored
Merge pull request #72 from delphi-hub/release
Release v1.2.0
2 parents e14e668 + 8c864b1 commit 64760ce

35 files changed

+2220
-1350
lines changed

.travis.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
language: scala
22
scala:
3-
- 2.12.4
3+
- 2.13.1
44
script:
55
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sbt ++$TRAVIS_SCALA_VERSION test; fi'
6-
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sbt ++$TRAVIS_SCALA_VERSION coverage test coverageReport coverageAggregate codacyCoverage; fi'
6+
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sbt ++$TRAVIS_SCALA_VERSION test; fi'
7+
# - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sbt ++$TRAVIS_SCALA_VERSION coverage test coverageReport coverageAggregate codacyCoverage; fi'
78
after_success:
8-
- 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash <(curl -s https://codecov.io/bash); fi'
9+
# - 'if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash <(curl -s https://codecov.io/bash); fi'
910
cache:
1011
directories:
1112
- $HOME/.cache/coursier

app/authorization/AuthProvider.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
// limitations under the License.
1616
package authorization
1717

18+
import java.time.Clock
19+
1820
import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim}
1921
import utils.CommonHelper
2022

2123
object AuthProvider {
2224

25+
implicit val clock: Clock = Clock.systemUTC
26+
2327
def generateJwt(validFor: Long = 1, useGenericName: Boolean = false): String = {
2428
val claim = JwtClaim()
2529
.issuedNow
@@ -32,4 +36,4 @@ object AuthProvider {
3236
Jwt.encode(claim, CommonHelper.configuration.jwtSecretKey, JwtAlgorithm.HS256)
3337
}
3438

35-
}
39+
}

app/controllers/HomeController.scala

Lines changed: 123 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,28 @@
1515
// limitations under the License.
1616
package controllers
1717

18-
18+
import scala.language.postfixOps
1919
import java.io.IOException
2020

2121
import akka.actor.ActorSystem
2222
import akka.http.scaladsl.Http
2323
import akka.http.scaladsl.model._
2424
import akka.http.scaladsl.marshalling.Marshal
2525
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
26+
import akka.http.scaladsl.unmarshalling.Unmarshal
2627
import akka.stream.ActorMaterializer
2728
import akka.util.ByteString
2829
import javax.inject._
2930
import play.api.Configuration
3031
import play.api.mvc._
3132
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
3436

3537
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._
3740

3841
import scala.concurrent.{Await, Future}
3942
import spray.json.DefaultJsonProtocol
@@ -47,6 +50,7 @@ import scala.concurrent.duration._
4750
class HomeController @Inject()(assets: Assets,configuration: Configuration, cc: ControllerComponents) extends AbstractController(cc)
4851
with SprayJsonSupport with DefaultJsonProtocol {
4952

53+
var featureList: List[InternalFeature] = List[InternalFeature]()
5054
/**
5155
* Create an Action to render an HTML page with a welcome message.
5256
* The configuration in the `routes` file means that this method
@@ -61,47 +65,87 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
6165
* @return
6266
*/
6367

64-
def query(query: String): Action[AnyContent] = Action.async {
68+
def search: Action[AnyContent] = Action.async {
6569
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
9289
}
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))
93100
}
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] = {
97119

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()))
101141
}
102142
}
143+
val result = Await.result(resultFuture, Duration.Inf)
144+
val queryResponse: Future[Result] = Future.successful(Ok(result.toString))
145+
queryResponse
103146
}
104147

148+
105149
/**
106150
* Get list of features
107151
*
@@ -114,14 +158,11 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
114158
implicit val system = ActorSystem()
115159
implicit val ec = system.dispatcher
116160
implicit val materializer = ActorMaterializer()
161+
implicit val queryFormat = jsonFormat2(InternalFeature)
117162

118163
val featuresUri = CommonHelper.getDelphiServer() + "/features"
119-
120164
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = featuresUri, method = HttpMethods.GET))
121-
122-
123165
val response = Await.result(responseFuture, 10 seconds)
124-
125166
val resultFuture: Future[String] = response match {
126167
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
127168
entity.dataBytes.runFold(ByteString(""))(_ ++ _).map { body =>
@@ -132,8 +173,9 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
132173
Future.failed(throw new IOException(code.defaultMessage()))
133174
}
134175
}
135-
136176
val result = Await.result(resultFuture, Duration.Inf)
177+
val featureListFuture = Unmarshal(result).to[List[InternalFeature]]
178+
featureList = Await.result(featureListFuture, Duration.Inf)
137179
val queryResponse: Future[Result] = Future.successful(Ok(result))
138180
queryResponse
139181
}
@@ -150,30 +192,52 @@ class HomeController @Inject()(assets: Assets,configuration: Configuration, cc:
150192
implicit val ec = system.dispatcher
151193
implicit val materializer = ActorMaterializer()
152194

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))
155202

156-
val response = Await.result(responseRetrieve, 10 seconds)
203+
val response = Await.result(responseRetrieve, 10 seconds)
157204

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()))
162213
}
163-
case resp@HttpResponse(code, _, _, _) => {
164-
resp.discardEntityBytes()
165-
Future.failed(throw new IOException(code.defaultMessage()))
166214
}
215+
216+
val result = Await.result(resultFuture, Duration.Inf)
217+
val retrieveResponse: Future[Result] = Future.successful(Ok(result))
218+
retrieveResponse
167219
}
220+
}
221+
}
168222

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()
172239
}
173240
}
174241

175-
case class Query(query: String, limit: Option[Int] = None)
242+
case class Query(query: String, limit: Option[Int] = Some(CommonHelper.defaultFetchSize))
176243
}
177-
178-
179-

app/models/QueryFormat.scala renamed to app/models/InternalFeature.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
// limitations under the License.
1616
package models
1717

18-
import play.api.libs.json.Json
18+
import spray.json.DefaultJsonProtocol
1919

20-
case class QueryFormat(query:String)
20+
case class InternalFeature(name : String, description : String)
2121

22-
object QueryFormat {
23-
implicit val writes = Json.writes[QueryFormat]
22+
object InternalFeatureJson extends DefaultJsonProtocol {
23+
implicit val featureFormat = jsonFormat2(InternalFeature)
2424
}

app/models/QueryRequestBody.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (C) 2018 The Delphi Team.
2+
// See the LICENCE file distributed with this work for additional
3+
// information regarding copyright ownership.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
package models
17+
18+
import play.api.libs.json._
19+
import utils.CommonHelper
20+
21+
case class QueryRequestBody (query : String, limit : Option[Int] = Some(CommonHelper.defaultFetchSize))
22+
23+
object QueryRequestBody {
24+
25+
implicit object QueryRequestBodyFormat extends Format[QueryRequestBody] {
26+
27+
implicit def reads(json: JsValue): JsResult[QueryRequestBody] = {
28+
val query = (json \ "query").as[String]
29+
val limit : Option[Int] = (json \ "limit").asOpt[Int]
30+
JsSuccess(QueryRequestBody(query, limit))
31+
}
32+
33+
def writes(q: QueryRequestBody): JsValue = {
34+
val limit = q.limit.getOrElse(CommonHelper.defaultFetchSize)
35+
val reqBody = Seq("query" -> JsString(q.query),
36+
"limit" -> JsNumber(limit))
37+
JsObject(reqBody)
38+
}
39+
}
40+
}

app/models/ResultToJson.scala

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)