|
16 | 16 |
|
17 | 17 | package de.upb.cs.swt.delphi.cli.commands
|
18 | 18 |
|
19 |
| -import java.util.concurrent.{TimeUnit, TimeoutException} |
20 |
| - |
21 |
| -import akka.actor.ActorSystem |
22 |
| -import akka.http.scaladsl.Http |
23 |
| -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport |
24 |
| -import akka.http.scaladsl.marshalling.Marshal |
25 |
| -import akka.http.scaladsl.model._ |
26 |
| -import akka.http.scaladsl.unmarshalling.Unmarshal |
27 |
| -import akka.stream.ActorMaterializer |
28 |
| -import akka.util.ByteString |
29 |
| -import de.upb.cs.swt.delphi.cli.Config |
| 19 | +import java.util.concurrent.TimeUnit |
| 20 | + |
| 21 | +import com.softwaremill.sttp._ |
| 22 | +import com.softwaremill.sttp.sprayJson._ |
30 | 23 | import de.upb.cs.swt.delphi.cli.artifacts.SearchResult
|
31 |
| -import de.upb.cs.swt.delphi.cli.artifacts.SearchResultJson._ |
32 |
| -import spray.json.DefaultJsonProtocol |
| 24 | +import de.upb.cs.swt.delphi.cli.{Config, artifacts} |
| 25 | +import spray.json._ |
33 | 26 |
|
34 | 27 | import scala.concurrent.duration._
|
35 |
| -import scala.concurrent.{Await, ExecutionContextExecutor, Future} |
36 |
| -import scala.util.{Failure, Success, Try} |
37 | 28 |
|
38 |
| -object SearchCommand extends Command with SprayJsonSupport with DefaultJsonProtocol { |
| 29 | +object SearchCommand extends Command with DefaultJsonProtocol{ |
39 | 30 |
|
40 |
| - val searchTimeout: Int = 10 |
| 31 | + val searchTimeout = 10.seconds |
| 32 | + val TIMEOUT_CODE = 408 |
41 | 33 |
|
42 | 34 | /**
|
43 | 35 | * Executes the command implementation
|
44 | 36 | *
|
45 | 37 | * @param config The current configuration for the command
|
46 | 38 | */
|
47 |
| - def execute(config: Config)(implicit system: ActorSystem): Unit = { |
48 |
| - implicit val ec = system.dispatcher |
49 |
| - implicit val materializer = ActorMaterializer() |
| 39 | + override def execute(implicit config: Config, backend: SttpBackend[Id, Nothing]): Unit = { |
50 | 40 |
|
51 | 41 | def query = config.query
|
52 | 42 |
|
53 |
| - information(config)(s"Searching for artifacts matching ${'"'}$query${'"'}.") |
54 |
| - val start = System.nanoTime() |
| 43 | + information.apply(s"Searching for artifacts matching ${'"'}$query${'"'}.") |
55 | 44 |
|
56 |
| - implicit val queryFormat = jsonFormat2(Query) |
57 |
| - val baseUri = Uri(config.server) |
58 |
| - val prettyParam = Map("pretty" -> "") |
59 |
| - val searchUri = baseUri.withPath(baseUri.path + "/search").withQuery(akka.http.scaladsl.model.Uri.Query(prettyParam)) |
60 |
| - val responseFuture = Marshal(Query(query, config.limit)).to[RequestEntity] flatMap { entity => |
61 |
| - Http().singleRequest(HttpRequest(uri = searchUri, method = HttpMethods.POST, entity = entity)) |
62 |
| - } |
63 | 45 |
|
64 |
| - Try(Await.result(responseFuture, Duration(config.timeout.getOrElse(searchTimeout) + " seconds"))). |
65 |
| - map(response => parseResponse(response, config, start)(ec, materializer)). |
66 |
| - recover { |
67 |
| - case e : TimeoutException => { |
68 |
| - error(config)("The query timed out after " + (System.nanoTime() - start).nanos.toUnit(TimeUnit.SECONDS) + |
69 |
| - " seconds. To set a longer timeout, use the --timeout option.") |
70 |
| - Failure(e) |
71 |
| - } |
72 |
| - } |
| 46 | + val queryParams = Map("pretty" -> "") |
| 47 | + val queryPayload: Query = Query(query,config.limit) |
| 48 | + val searchUri = uri"${config.server}/search?$queryParams" |
| 49 | + |
| 50 | + val request = sttp.body(queryPayload.toJson).post(searchUri) |
| 51 | + |
| 52 | + val (res, time) = processRequest(request) |
| 53 | + res.foreach(processResults(_, time)) |
73 | 54 | }
|
74 | 55 |
|
75 |
| - private def parseResponse(response: HttpResponse, config: Config, start: Long) |
76 |
| - (implicit ec: ExecutionContextExecutor, materializer: ActorMaterializer): Unit = { |
| 56 | + private def processRequest(req: Request[String, Nothing]) |
| 57 | + (implicit config: Config, |
| 58 | + backend: SttpBackend[Id, Nothing]): (Option[String], FiniteDuration) = { |
| 59 | + val start = System.nanoTime() |
| 60 | + val res: Id[Response[String]] = req.readTimeout(searchTimeout).send() |
| 61 | + val end = System.nanoTime() |
| 62 | + val took = (end - start).nanos |
77 | 63 |
|
78 |
| - val resultFuture: Future[String] = response match { |
79 |
| - case HttpResponse(StatusCodes.OK, headers, entity, _) => |
80 |
| - entity.dataBytes.runFold(ByteString(""))(_ ++ _).map { body => |
81 |
| - body.utf8String |
82 |
| - } |
83 |
| - case resp@HttpResponse(code, _, _, _) => { |
84 |
| - error(config)("Request failed, response code: " + code) |
85 |
| - resp.discardEntityBytes() |
86 |
| - Future("") |
87 |
| - } |
88 |
| - } |
| 64 | + if (res.code == TIMEOUT_CODE) { |
89 | 65 |
|
90 |
| - val result = Await.result(resultFuture, Duration.Inf) |
| 66 | + error.apply(s"The query timed out after ${took.toSeconds} seconds. " + |
| 67 | + "To set a longer timeout, use the --timeout option.") |
| 68 | + } |
| 69 | + val resStr = res.body match { |
| 70 | + case Left(v) => |
| 71 | + error.apply(s"Search request failed \n $v") |
| 72 | + println(v) |
| 73 | + None |
| 74 | + case Right(v) => |
| 75 | + Some(v) |
| 76 | + } |
| 77 | + (resStr, took) |
| 78 | + } |
91 | 79 |
|
92 |
| - val took = (System.nanoTime() - start).nanos.toUnit(TimeUnit.SECONDS) |
| 80 | + private def processResults(res: String, queryRuntime: FiniteDuration)(implicit config: Config) = { |
93 | 81 |
|
94 |
| - if (config.raw || result.equals("")) { |
95 |
| - reportResult(config)(result) |
| 82 | + if (config.raw || res.equals("")) { |
| 83 | + reportResult.apply(res) |
| 84 | + } |
| 85 | + if (!(config.raw || res.equals("")) || !config.csv.equals("")) { |
| 86 | + import artifacts.SearchResultJson._ |
| 87 | + val jsonArr = res.parseJson.asInstanceOf[JsArray].elements |
| 88 | + val retrieveResults = jsonArr.map(r => r.convertTo[SearchResult]).toList |
| 89 | + onProperSearchResults(retrieveResults) |
96 | 90 | }
|
97 | 91 |
|
98 |
| - if(!(config.raw || result.equals("")) || !config.csv.equals("")) { |
99 |
| - val unmarshalledFuture = Unmarshal(result).to[List[SearchResult]] |
| 92 | + def onProperSearchResults(sr: List[SearchResult]) = { |
100 | 93 |
|
101 |
| - val processFuture = unmarshalledFuture.transform { |
102 |
| - case Success(unmarshalled) => { |
103 |
| - processResults(config, unmarshalled, took) |
104 |
| - Success(unmarshalled) |
105 |
| - } |
106 |
| - case Failure(e) => { |
107 |
| - error(config)(result) |
108 |
| - Failure(e) |
| 94 | + val capMessage = { |
| 95 | + config.limit match { |
| 96 | + case Some(limit) if (limit <= sr.size) |
| 97 | + => s"Results may be capped by result limit set to $limit." |
| 98 | + case None if (sr.size >= 50) |
| 99 | + => "Results may be capped by default limit of 50 returned results. Use --limit to extend the result set." |
| 100 | + case _ |
| 101 | + => "" |
109 | 102 | }
|
110 | 103 | }
|
111 |
| - } |
112 |
| - } |
113 | 104 |
|
114 |
| - private def processResults(config: Config, results: List[SearchResult], queryRuntime: Double) = { |
115 |
| - val capMessage = { |
116 |
| - config.limit match { |
117 |
| - case Some(limit) if (limit <= results.size) |
118 |
| - => s"Results may be capped by result limit set to $limit." |
119 |
| - case None if (results.size >= 50) |
120 |
| - => "Results may be capped by default limit of 50 returned results. Use --limit to extend the result set." |
121 |
| - case _ |
122 |
| - => "" |
123 |
| - } |
124 |
| - } |
125 |
| - success(config)(s"Found ${results.size} item(s). $capMessage") |
126 |
| - reportResult(config)(results) |
| 105 | + success.apply(s"Found ${sr.size} item(s). $capMessage") |
| 106 | + reportResult.apply(sr) |
127 | 107 |
|
128 |
| - information(config)(f"Query took $queryRuntime%.2fs.") |
| 108 | + information.apply(f"Query took ${queryRuntime.toUnit(TimeUnit.SECONDS)}%.2fs.") |
129 | 109 |
|
130 |
| - if(!config.csv.equals("")) { |
131 |
| - exportResult(config)(results) |
132 |
| - information(config)("Results written to file '" + config.csv + "'") |
| 110 | + if (!config.csv.equals("")) { |
| 111 | + exportResult.apply(sr) |
| 112 | + information.apply("Results written to file '" + config.csv + "'") |
| 113 | + } |
133 | 114 | }
|
134 | 115 | }
|
135 | 116 |
|
136 |
| - case class Query(query: String, |
137 |
| - limit: Option[Int] = None) |
138 |
| - |
139 | 117 | }
|
0 commit comments