1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2018 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.api.post
21 
22 import com.sksamuel.elastic4s.Index
23 import com.sksamuel.elastic4s.circe._
24 import com.sksamuel.elastic4s.ElasticDsl._
25 import com.sksamuel.elastic4s.requests.common.RefreshPolicy
26 import com.sksamuel.elastic4s.requests.searches.SearchRequest
27 import com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery
28 import grizzled.slf4j.Logging
29 import io.circe.{Json, Printer}
30 import org.make.api.technical.elasticsearch._
31 import org.make.core.CirceFormatters
32 import org.make.core.elasticsearch.IndexationStatus
33 import org.make.core.post.PostId
34 import org.make.core.post.indexed._
35 
36 import scala.concurrent.ExecutionContext.Implicits.global
37 import scala.concurrent.Future
38 
39 trait PostSearchEngineComponent {
40   def elasticsearchPostAPI: PostSearchEngine
41 }
42 
43 trait PostSearchEngine {
44   def findPostById(postId: PostId): Future[Option[IndexedPost]]
45   def searchPosts(query: PostSearchQuery): Future[PostSearchResult]
46   def indexPosts(records: Seq[IndexedPost], maybeIndex: Option[Index]): Future[IndexationStatus]
47 }
48 
49 object PostSearchEngine {
50   val postIndexName: String = "post"
51 }
52 
53 trait DefaultPostSearchEngineComponent extends PostSearchEngineComponent with CirceFormatters with Logging {
54   self: ElasticsearchConfigurationComponent with ElasticsearchClientComponent =>
55 
56   override lazy val elasticsearchPostAPI: PostSearchEngine =
57     new DefaultPostSearchEngine
58 
59   class DefaultPostSearchEngine extends PostSearchEngine {
60 
61     private lazy val client = elasticsearchClient.client
62 
63     private val postAlias: Index = elasticsearchConfiguration.postAliasName
64 
65     // TODO remove once elastic4s-circe upgrades to circe 0.14
66     private implicit val printer: Json => String = Printer.noSpaces.print
67 
68     override def findPostById(postId: PostId): Future[Option[IndexedPost]] = {
69       client
70         .executeAsFuture(get(postAlias, postId.value))
71         .map(_.toOpt[IndexedPost])
72     }
73 
74     override def searchPosts(query: PostSearchQuery): Future[PostSearchResult] = {
75       val searchFilters = PostSearchFilters.getPostSearchFilters(query)
76       val request: SearchRequest =
77         search(postAlias)
78           .bool(BoolQuery(must = searchFilters))
79           .sortBy(PostSearchFilters.getSort(query).toList)
80           .size(PostSearchFilters.getLimitSearch(query))
81           .from(PostSearchFilters.getOffsetSearch(query))
82           .trackTotalHits(true)
83 
84       client
85         .executeAsFuture(request)
86         .map { response =>
87           PostSearchResult(total = response.totalHits, results = response.to[IndexedPost])
88         }
89     }
90 
91     override def indexPosts(records: Seq[IndexedPost], maybeIndex: Option[Index]): Future[IndexationStatus] = {
92       val index = maybeIndex.getOrElse(postAlias)
93       client
94         .executeAsFuture(bulk(records.map { record =>
95           indexInto(index).doc(record).refresh(RefreshPolicy.IMMEDIATE).id(record.postId.value)
96         }))
97         .map(_ => IndexationStatus.Completed)
98         .recover {
99           case e: Exception =>
100             logger.error(s"Indexing ${records.size} posts failed", e)
101             IndexationStatus.Failed(e)
102         }
103     }
104   }
105 }
Line Stmt Id Pos Tree Symbol Tests Code
50 8555 1838 - 1844 Literal <nosymbol> "post"
63 8160 2286 - 2326 ApplyImplicitView com.sksamuel.elastic4s.Index.toIndex org.scalatest.testsuite elastic4s.this.Index.toIndex(DefaultPostSearchEngineComponent.this.elasticsearchConfiguration.postAliasName)
66 8785 2442 - 2464 Apply io.circe.Printer.print io.circe.Printer.noSpaces.print(json)
70 8863 2583 - 2611 Apply com.sksamuel.elastic4s.api.GetApi.get org.scalatest.testsuite com.sksamuel.elastic4s.ElasticDsl.get(DefaultPostSearchEngine.this.postAlias, postId.value)
70 8118 2582 - 2582 Select scala.concurrent.ExecutionContext.Implicits.global org.scalatest.testsuite scala.concurrent.ExecutionContext.Implicits.global
70 8181 2598 - 2610 Select org.make.core.post.PostId.value org.scalatest.testsuite postId.value
70 8402 2587 - 2596 Select org.make.api.post.DefaultPostSearchEngineComponent.DefaultPostSearchEngine.postAlias org.scalatest.testsuite DefaultPostSearchEngine.this.postAlias
70 8473 2582 - 2582 Select com.sksamuel.elastic4s.handlers.get.GetHandlers.GetHandler org.scalatest.testsuite com.sksamuel.elastic4s.ElasticDsl.GetHandler
70 8721 2582 - 2582 Apply scala.reflect.ManifestFactory.classType org.scalatest.testsuite scala.reflect.ManifestFactory.classType[com.sksamuel.elastic4s.requests.get.GetResponse](classOf[com.sksamuel.elastic4s.requests.get.GetResponse])
71 8149 2633 - 2633 ApplyToImplicitArgs com.sksamuel.elastic4s.circe.hitReaderWithCirce com.sksamuel.elastic4s.circe.`package`.hitReaderWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.decoder)
71 8406 2625 - 2625 Select scala.concurrent.ExecutionContext.Implicits.global org.scalatest.testsuite scala.concurrent.ExecutionContext.Implicits.global
71 8791 2626 - 2646 ApplyToImplicitArgs com.sksamuel.elastic4s.Hit.toOpt x$1.toOpt[org.make.core.post.indexed.IndexedPost](com.sksamuel.elastic4s.circe.`package`.hitReaderWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.decoder))
71 8031 2551 - 2647 ApplyToImplicitArgs scala.concurrent.Future.map org.scalatest.testsuite org.make.api.technical.elasticsearch.`package`.RichHttpClient(DefaultPostSearchEngine.this.client).executeAsFuture[com.sksamuel.elastic4s.requests.get.GetRequest, com.sksamuel.elastic4s.requests.get.GetResponse](com.sksamuel.elastic4s.ElasticDsl.get(DefaultPostSearchEngine.this.postAlias, postId.value))(com.sksamuel.elastic4s.ElasticDsl.GetHandler, scala.concurrent.ExecutionContext.Implicits.global, scala.reflect.ManifestFactory.classType[com.sksamuel.elastic4s.requests.get.GetResponse](classOf[com.sksamuel.elastic4s.requests.get.GetResponse])).map[Option[org.make.core.post.indexed.IndexedPost]](((x$1: com.sksamuel.elastic4s.requests.get.GetResponse) => x$1.toOpt[org.make.core.post.indexed.IndexedPost](com.sksamuel.elastic4s.circe.`package`.hitReaderWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.decoder))))(scala.concurrent.ExecutionContext.Implicits.global)
71 8496 2633 - 2633 Select org.make.core.post.indexed.IndexedPost.decoder indexed.this.IndexedPost.decoder
75 8840 2764 - 2809 Apply org.make.core.post.indexed.PostSearchFilters.getPostSearchFilters org.scalatest.testsuite org.make.core.post.indexed.PostSearchFilters.getPostSearchFilters(query)
82 8457 2853 - 3125 Apply com.sksamuel.elastic4s.requests.searches.SearchRequest.trackTotalHits org.scalatest.testsuite com.sksamuel.elastic4s.ElasticDsl.search(DefaultPostSearchEngine.this.postAlias).bool({ <artifact> val x$1: Seq[com.sksamuel.elastic4s.requests.searches.queries.Query] @scala.reflect.internal.annotations.uncheckedBounds = searchFilters; <artifact> val x$2: Option[Boolean] @scala.reflect.internal.annotations.uncheckedBounds = com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply$default$1; <artifact> val x$3: Option[Double] @scala.reflect.internal.annotations.uncheckedBounds = com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply$default$2; <artifact> val x$4: Option[String] @scala.reflect.internal.annotations.uncheckedBounds = com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply$default$3; <artifact> val x$5: Option[String] @scala.reflect.internal.annotations.uncheckedBounds = com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply$default$4; <artifact> val x$6: Seq[com.sksamuel.elastic4s.requests.searches.queries.Query] @scala.reflect.internal.annotations.uncheckedBounds = com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply$default$5; <artifact> val x$7: Seq[com.sksamuel.elastic4s.requests.searches.queries.Query] @scala.reflect.internal.annotations.uncheckedBounds = com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply$default$7; <artifact> val x$8: Seq[com.sksamuel.elastic4s.requests.searches.queries.Query] @scala.reflect.internal.annotations.uncheckedBounds = com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply$default$8; com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery.apply(x$2, x$3, x$4, x$5, x$6, x$1, x$7, x$8) }).sortBy(org.make.core.post.indexed.PostSearchFilters.getSort(query).toList).size(org.make.core.post.indexed.PostSearchFilters.getLimitSearch(query)).from(org.make.core.post.indexed.PostSearchFilters.getOffsetSearch(query)).trackTotalHits(true)
85 8533 3164 - 3164 Apply scala.reflect.ManifestFactory.classType org.scalatest.testsuite scala.reflect.ManifestFactory.classType[com.sksamuel.elastic4s.requests.searches.SearchResponse](classOf[com.sksamuel.elastic4s.requests.searches.SearchResponse])
85 8724 3164 - 3164 Select scala.concurrent.ExecutionContext.Implicits.global org.scalatest.testsuite scala.concurrent.ExecutionContext.Implicits.global
85 8111 3164 - 3164 Select com.sksamuel.elastic4s.requests.searches.SearchHandlers.SearchHandler org.scalatest.testsuite com.sksamuel.elastic4s.ElasticDsl.SearchHandler
86 8071 3133 - 3301 ApplyToImplicitArgs scala.concurrent.Future.map org.scalatest.testsuite org.make.api.technical.elasticsearch.`package`.RichHttpClient(DefaultPostSearchEngine.this.client).executeAsFuture[com.sksamuel.elastic4s.requests.searches.SearchRequest, com.sksamuel.elastic4s.requests.searches.SearchResponse](request)(com.sksamuel.elastic4s.ElasticDsl.SearchHandler, scala.concurrent.ExecutionContext.Implicits.global, scala.reflect.ManifestFactory.classType[com.sksamuel.elastic4s.requests.searches.SearchResponse](classOf[com.sksamuel.elastic4s.requests.searches.SearchResponse])).map[org.make.core.post.indexed.PostSearchResult](((response: com.sksamuel.elastic4s.requests.searches.SearchResponse) => org.make.core.post.indexed.PostSearchResult.apply(response.totalHits, response.to[org.make.core.post.indexed.IndexedPost](com.sksamuel.elastic4s.circe.`package`.hitReaderWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.decoder), (ClassTag.apply[org.make.core.post.indexed.IndexedPost](classOf[org.make.core.post.indexed.IndexedPost]): scala.reflect.ClassTag[org.make.core.post.indexed.IndexedPost])))))(scala.concurrent.ExecutionContext.Implicits.global)
86 8461 3187 - 3187 Select scala.concurrent.ExecutionContext.Implicits.global org.scalatest.testsuite scala.concurrent.ExecutionContext.Implicits.global
87 8152 3236 - 3254 Select com.sksamuel.elastic4s.requests.searches.SearchResponse.totalHits response.totalHits
87 8857 3211 - 3291 Apply org.make.core.post.indexed.PostSearchResult.apply org.make.core.post.indexed.PostSearchResult.apply(response.totalHits, response.to[org.make.core.post.indexed.IndexedPost](com.sksamuel.elastic4s.circe.`package`.hitReaderWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.decoder), (ClassTag.apply[org.make.core.post.indexed.IndexedPost](classOf[org.make.core.post.indexed.IndexedPost]): scala.reflect.ClassTag[org.make.core.post.indexed.IndexedPost])))
87 8034 3266 - 3290 ApplyToImplicitArgs com.sksamuel.elastic4s.requests.searches.SearchResponse.to response.to[org.make.core.post.indexed.IndexedPost](com.sksamuel.elastic4s.circe.`package`.hitReaderWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.decoder), (ClassTag.apply[org.make.core.post.indexed.IndexedPost](classOf[org.make.core.post.indexed.IndexedPost]): scala.reflect.ClassTag[org.make.core.post.indexed.IndexedPost]))
87 8386 3277 - 3277 ApplyToImplicitArgs com.sksamuel.elastic4s.circe.hitReaderWithCirce com.sksamuel.elastic4s.circe.`package`.hitReaderWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.decoder)
87 8792 3277 - 3277 Select org.make.core.post.indexed.IndexedPost.decoder indexed.this.IndexedPost.decoder
92 8708 3460 - 3469 Select org.make.api.post.DefaultPostSearchEngineComponent.DefaultPostSearchEngine.postAlias DefaultPostSearchEngine.this.postAlias
92 8312 3439 - 3470 Apply scala.Option.getOrElse maybeIndex.getOrElse[com.sksamuel.elastic4s.Index](DefaultPostSearchEngine.this.postAlias)
94 8789 3508 - 3508 Apply scala.reflect.ManifestFactory.classType scala.reflect.ManifestFactory.classType[com.sksamuel.elastic4s.requests.bulk.BulkResponse](classOf[com.sksamuel.elastic4s.requests.bulk.BulkResponse])
94 8710 3509 - 3644 Apply com.sksamuel.elastic4s.api.BulkApi.bulk com.sksamuel.elastic4s.ElasticDsl.bulk(records.map[com.sksamuel.elastic4s.requests.indexes.IndexRequest](((record: org.make.core.post.indexed.IndexedPost) => com.sksamuel.elastic4s.ElasticDsl.indexInto(index).doc[org.make.core.post.indexed.IndexedPost](record)(com.sksamuel.elastic4s.circe.`package`.indexableWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.encoder, DefaultPostSearchEngine.this.printer)).refresh(com.sksamuel.elastic4s.requests.common.RefreshPolicy.IMMEDIATE).id(record.postId.value))))
94 8360 3508 - 3508 Select com.sksamuel.elastic4s.handlers.bulk.BulkHandlers.BulkHandler com.sksamuel.elastic4s.ElasticDsl.BulkHandler
94 8060 3514 - 3643 Apply scala.collection.IterableOps.map records.map[com.sksamuel.elastic4s.requests.indexes.IndexRequest](((record: org.make.core.post.indexed.IndexedPost) => com.sksamuel.elastic4s.ElasticDsl.indexInto(index).doc[org.make.core.post.indexed.IndexedPost](record)(com.sksamuel.elastic4s.circe.`package`.indexableWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.encoder, DefaultPostSearchEngine.this.printer)).refresh(com.sksamuel.elastic4s.requests.common.RefreshPolicy.IMMEDIATE).id(record.postId.value)))
94 8179 3508 - 3508 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
95 8391 3568 - 3568 ApplyToImplicitArgs com.sksamuel.elastic4s.circe.indexableWithCirce com.sksamuel.elastic4s.circe.`package`.indexableWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.encoder, DefaultPostSearchEngine.this.printer)
95 8135 3568 - 3568 Select org.make.core.post.indexed.IndexedPost.encoder indexed.this.IndexedPost.encoder
95 8440 3548 - 3633 Apply com.sksamuel.elastic4s.requests.indexes.IndexRequest.id com.sksamuel.elastic4s.ElasticDsl.indexInto(index).doc[org.make.core.post.indexed.IndexedPost](record)(com.sksamuel.elastic4s.circe.`package`.indexableWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.encoder, DefaultPostSearchEngine.this.printer)).refresh(com.sksamuel.elastic4s.requests.common.RefreshPolicy.IMMEDIATE).id(record.postId.value)
95 8775 3568 - 3568 Select org.make.api.post.DefaultPostSearchEngineComponent.DefaultPostSearchEngine.printer DefaultPostSearchEngine.this.printer
95 8024 3585 - 3608 Select com.sksamuel.elastic4s.requests.common.RefreshPolicy.IMMEDIATE com.sksamuel.elastic4s.requests.common.RefreshPolicy.IMMEDIATE
95 8861 3613 - 3632 Select org.make.core.post.PostId.value record.postId.value
97 8374 3664 - 3690 Select org.make.core.elasticsearch.IndexationStatus.Completed org.make.core.elasticsearch.IndexationStatus.Completed
97 8051 3658 - 3658 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
98 8109 3709 - 3709 Apply org.make.api.post.DefaultPostSearchEngineComponent.DefaultPostSearchEngine.$anonfun.<init> new $anonfun()
98 8363 3477 - 3860 ApplyToImplicitArgs scala.concurrent.Future.recover org.make.api.technical.elasticsearch.`package`.RichHttpClient(DefaultPostSearchEngine.this.client).executeAsFuture[com.sksamuel.elastic4s.requests.bulk.BulkRequest, com.sksamuel.elastic4s.requests.bulk.BulkResponse](com.sksamuel.elastic4s.ElasticDsl.bulk(records.map[com.sksamuel.elastic4s.requests.indexes.IndexRequest](((record: org.make.core.post.indexed.IndexedPost) => com.sksamuel.elastic4s.ElasticDsl.indexInto(index).doc[org.make.core.post.indexed.IndexedPost](record)(com.sksamuel.elastic4s.circe.`package`.indexableWithCirce[org.make.core.post.indexed.IndexedPost](indexed.this.IndexedPost.encoder, DefaultPostSearchEngine.this.printer)).refresh(com.sksamuel.elastic4s.requests.common.RefreshPolicy.IMMEDIATE).id(record.postId.value)))))(com.sksamuel.elastic4s.ElasticDsl.BulkHandler, scala.concurrent.ExecutionContext.Implicits.global, scala.reflect.ManifestFactory.classType[com.sksamuel.elastic4s.requests.bulk.BulkResponse](classOf[com.sksamuel.elastic4s.requests.bulk.BulkResponse])).map[org.make.core.elasticsearch.IndexationStatus.Completed.type](((x$2: com.sksamuel.elastic4s.requests.bulk.BulkResponse) => org.make.core.elasticsearch.IndexationStatus.Completed))(scala.concurrent.ExecutionContext.Implicits.global).recover[org.make.core.elasticsearch.IndexationStatus](({ @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractPartialFunction[Throwable,org.make.core.elasticsearch.IndexationStatus] with java.io.Serializable { def <init>(): <$anon: Throwable => org.make.core.elasticsearch.IndexationStatus> = { $anonfun.super.<init>(); () }; final override def applyOrElse[A1 <: Throwable, B1 >: org.make.core.elasticsearch.IndexationStatus](x1: A1, default: A1 => B1): B1 = ((x1.asInstanceOf[Throwable]: Throwable): Throwable @unchecked) match { case (e @ (_: Exception)) => { DefaultPostSearchEngineComponent.this.logger.error(("Indexing ".+(records.size).+(" posts failed"): String), e); org.make.core.elasticsearch.IndexationStatus.Failed.apply(e) } case (defaultCase$ @ _) => default.apply(x1) }; final def isDefinedAt(x1: Throwable): Boolean = ((x1.asInstanceOf[Throwable]: Throwable): Throwable @unchecked) match { case (e @ (_: Exception)) => true case (defaultCase$ @ _) => false } }; new $anonfun() }: PartialFunction[Throwable,org.make.core.elasticsearch.IndexationStatus]))(scala.concurrent.ExecutionContext.Implicits.global)
98 8691 3709 - 3709 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
100 8804 3754 - 3811 Apply grizzled.slf4j.Logger.error DefaultPostSearchEngineComponent.this.logger.error(("Indexing ".+(records.size).+(" posts failed"): String), e)
101 8495 3824 - 3850 Apply org.make.core.elasticsearch.IndexationStatus.Failed.apply org.make.core.elasticsearch.IndexationStatus.Failed.apply(e)