Browse Source

Added tcp server to read from a csv stream. Added GET support for search endpoint. Some cleanup

Abhinav Sarkar 7 years ago
parent
commit
52966fbd8c

+ 5
- 0
pom.xml View File

@@ -73,6 +73,11 @@
73 73
 			<artifactId>lucene-queryparser</artifactId>
74 74
 			<version>${lucene.version}</version>
75 75
 		</dependency>
76
+		<dependency>
77
+			<groupId>net.sf.opencsv</groupId>
78
+			<artifactId>opencsv</artifactId>
79
+			<version>2.3</version>
80
+		</dependency>
76 81
 	</dependencies>
77 82
 
78 83
 	<build>

+ 11
- 13
src/main/scala/net/abhinavsarkar/ircsearch/HttpRequestHandler.scala View File

@@ -1,21 +1,19 @@
1 1
 package net.abhinavsarkar.ircsearch
2 2
 
3
-import io.netty.channel.ChannelInboundMessageHandlerAdapter
4
-import io.netty.handler.codec.http.HttpRequest
5 3
 import com.typesafe.scalalogging.slf4j.Logging
6
-import io.netty.channel.ChannelHandlerContext
7
-import io.netty.handler.codec.http.HttpResponse
8
-import io.netty.channel.ChannelFuture
9
-import io.netty.handler.codec.http.HttpHeaders.isKeepAlive
10
-import io.netty.handler.codec.http.HttpHeaders.Names._
11
-import io.netty.handler.codec.http.HttpHeaders
4
+
5
+import io.netty.buffer.Unpooled
12 6
 import io.netty.channel.ChannelFutureListener
7
+import io.netty.channel.ChannelHandlerContext
8
+import io.netty.channel.ChannelInboundMessageHandlerAdapter
13 9
 import io.netty.handler.codec.http.DefaultHttpResponse
10
+import io.netty.handler.codec.http.HttpHeaders
11
+import io.netty.handler.codec.http.HttpHeaders.Names._
12
+import io.netty.handler.codec.http.HttpHeaders.isKeepAlive
13
+import io.netty.handler.codec.http.HttpRequest
14
+import io.netty.handler.codec.http.HttpResponse
14 15
 import io.netty.handler.codec.http.HttpResponseStatus._
15
-import io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
16
-import io.netty.handler.codec.http.HttpVersion
17
-import io.netty.handler.codec.http.HttpResponseStatus
18
-import io.netty.buffer.Unpooled
16
+import io.netty.handler.codec.http.HttpVersion.HTTP_1_1
19 17
 
20 18
 
21 19
 trait HttpRequestHandler extends ChannelInboundMessageHandlerAdapter[HttpRequest] with Logging {
@@ -34,7 +32,7 @@ trait HttpRequestHandler extends ChannelInboundMessageHandlerAdapter[HttpRequest
34 32
   }
35 33
 
36 34
   protected def sendSuccess(ctx : ChannelHandlerContext, request : HttpRequest, body : String) : HttpResponse = {
37
-    val response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
35
+    val response = new DefaultHttpResponse(HTTP_1_1, OK)
38 36
     response.setContent(Unpooled.copiedBuffer(body.getBytes))
39 37
     response.setHeader(CONTENT_TYPE, "application/json")
40 38
     writeResponse(ctx, request, response)

+ 2
- 3
src/main/scala/net/abhinavsarkar/ircsearch/HttpRequestRouter.scala View File

@@ -1,7 +1,6 @@
1 1
 package net.abhinavsarkar.ircsearch
2 2
 
3 3
 import io.netty.channel.ChannelHandler.Sharable
4
-import java.util.regex.Pattern
5 4
 import io.netty.channel.ChannelHandlerContext
6 5
 import io.netty.handler.codec.http.HttpRequest
7 6
 
@@ -15,12 +14,12 @@ abstract class HttpRequestRouter extends HttpRequestHandler {
15 14
       val uri = request.getUri
16 15
       if (route.isDefinedAt(uri)) {
17 16
         val routeHandler = route.apply(uri)
18
-        ctx.pipeline.addLast("handler", routeHandler)
17
+        ctx.pipeline.addLast("httphandler", routeHandler)
19 18
         try {
20 19
             ctx.nextInboundMessageBuffer.add(request)
21 20
             ctx.fireInboundBufferUpdated
22 21
         } finally {
23
-            ctx.pipeline.remove("handler")
22
+            ctx.pipeline.remove("httphandler")
24 23
         }
25 24
       } else {
26 25
         logRequest(ctx, request, sendNotFound(ctx, request))

+ 110
- 25
src/main/scala/net/abhinavsarkar/ircsearch/Server.scala View File

@@ -3,27 +3,36 @@ package net.abhinavsarkar.ircsearch
3 3
 import java.net.InetSocketAddress
4 4
 import java.nio.charset.Charset
5 5
 
6
+import scala.collection.JavaConversions._
6 7
 import scala.concurrent.ExecutionContext.Implicits._
7 8
 import scala.concurrent.future
8 9
 
9 10
 import com.typesafe.scalalogging.slf4j.Logging
10 11
 
12
+import au.com.bytecode.opencsv.CSVParser
11 13
 import io.netty.bootstrap.ServerBootstrap
14
+import io.netty.buffer.ByteBuf
12 15
 import io.netty.channel.ChannelHandler.Sharable
13 16
 import io.netty.channel.ChannelHandlerContext
17
+import io.netty.channel.ChannelInboundByteHandlerAdapter
18
+import io.netty.channel.ChannelInboundMessageHandlerAdapter
14 19
 import io.netty.channel.ChannelInitializer
15 20
 import io.netty.channel.socket.SocketChannel
16 21
 import io.netty.channel.socket.nio.NioEventLoopGroup
17 22
 import io.netty.channel.socket.nio.NioServerSocketChannel
23
+import io.netty.handler.codec.DelimiterBasedFrameDecoder
24
+import io.netty.handler.codec.Delimiters
18 25
 import io.netty.handler.codec.http.HttpChunkAggregator
19 26
 import io.netty.handler.codec.http.HttpContentCompressor
27
+import io.netty.handler.codec.http.HttpMethod
20 28
 import io.netty.handler.codec.http.HttpRequest
21 29
 import io.netty.handler.codec.http.HttpRequestDecoder
22 30
 import io.netty.handler.codec.http.HttpResponseEncoder
31
+import io.netty.handler.codec.http.QueryStringDecoder
32
+import io.netty.handler.codec.string.StringDecoder
23 33
 import net.abhinavsarkar.ircsearch.lucene.Indexer
24 34
 import net.abhinavsarkar.ircsearch.lucene.Searcher
25
-import net.abhinavsarkar.ircsearch.model.IndexRequest
26
-import net.abhinavsarkar.ircsearch.model.SearchRequest
35
+import net.abhinavsarkar.ircsearch.model._
27 36
 import net.liftweb.json.DefaultFormats
28 37
 import net.liftweb.json.Serialization
29 38
 
@@ -36,28 +45,13 @@ object Server extends App with Logging {
36 45
     val port = args(0).toInt
37 46
     logger.info("Starting server at port {}", port: Integer)
38 47
 
39
-    val httpRequestRouter = new HttpRequestRouter {
40
-      val Echo = "^/echo$".r
41
-      val Index = "^/index$".r
42
-      val Search = "^/search$".r
43
-      def route = {
44
-        case Echo() => EchoHandler
45
-        case Index() => IndexHandler
46
-        case Search() => SearchHandler
47
-      }
48
-    }
49
-
50 48
     val server = (new ServerBootstrap)
51 49
       .group(new NioEventLoopGroup(1), new NioEventLoopGroup(1))
52 50
       .channel(classOf[NioServerSocketChannel])
53 51
       .childHandler(new ChannelInitializer[SocketChannel] {
54 52
         def initChannel(ch: SocketChannel) {
55 53
           val p = ch.pipeline
56
-            .addLast("decoder", new HttpRequestDecoder)
57
-            .addLast("aggregator", new HttpChunkAggregator(1048576))
58
-            .addLast("encoder", new HttpResponseEncoder)
59
-            .addLast("compressor", new HttpContentCompressor)
60
-            .addLast("router", httpRequestRouter)
54
+            .addLast("unihandler", UnifiedHandler)
61 55
         }})
62 56
       .localAddress(new InetSocketAddress(port))
63 57
 
@@ -65,7 +59,7 @@ object Server extends App with Logging {
65 59
       new Thread("ShutdownHook") {
66 60
         override def run {
67 61
           stopServer(server)
68
-          IndexHandler.stop
62
+          UnifiedHandler.stop
69 63
         }
70 64
       })
71 65
 
@@ -75,7 +69,7 @@ object Server extends App with Logging {
75 69
       case e : Exception => {
76 70
         logger.error("Exception while running server. Stopping server", e)
77 71
         stopServer(server)
78
-        IndexHandler.stop
72
+        UnifiedHandler.stop
79 73
       }
80 74
     }
81 75
   }
@@ -88,6 +82,86 @@ object Server extends App with Logging {
88 82
 
89 83
 }
90 84
 
85
+@Sharable
86
+object UnifiedHandler extends ChannelInboundByteHandlerAdapter {
87
+
88
+  lazy val indexer = { val indexer = new Indexer; indexer.start; indexer }
89
+
90
+  val httpRequestRouter = new HttpRequestRouter {
91
+    val Echo = "^/echo$".r
92
+    val Index = "^/index$".r
93
+    val Search = "^/search.*".r
94
+    def route = {
95
+      case Echo() => EchoHandler
96
+      case Index() => new IndexHandler(indexer)
97
+      case Search() => SearchHandler
98
+    }
99
+  }
100
+
101
+  def stop = indexer.stop
102
+
103
+  override def inboundBufferUpdated(ctx : ChannelHandlerContext, in: ByteBuf) {
104
+    if (in.readableBytes() < 5) {
105
+      return;
106
+    }
107
+
108
+    val magic1 = in.getUnsignedByte(in.readerIndex())
109
+    val magic2 = in.getUnsignedByte(in.readerIndex() + 1)
110
+    if (isHttp(magic1, magic2)) {
111
+      ctx.pipeline
112
+        .addLast("decoder", new HttpRequestDecoder)
113
+        .addLast("aggregator", new HttpChunkAggregator(1048576))
114
+        .addLast("encoder", new HttpResponseEncoder)
115
+        .addLast("compressor", new HttpContentCompressor)
116
+        .addLast("router", httpRequestRouter)
117
+        .remove(this)
118
+    } else {
119
+      ctx.pipeline
120
+        .addLast("framedecoder", new DelimiterBasedFrameDecoder(1048576, Delimiters.lineDelimiter() : _*))
121
+        .addLast("decoder", new StringDecoder(Charset.forName("UTF-8")))
122
+        .addLast("csvhandler", new TcpIndexHandler(indexer))
123
+        .remove(this)
124
+    }
125
+    ctx.nextInboundByteBuffer.writeBytes(in)
126
+    ctx.fireInboundBufferUpdated
127
+  }
128
+
129
+  private def isHttp(magic1: Int, magic2: Int) = {
130
+      magic1 == 'G' && magic2 == 'E' || // GET
131
+      magic1 == 'P' && magic2 == 'O' || // POST
132
+      magic1 == 'P' && magic2 == 'U' || // PUT
133
+      magic1 == 'H' && magic2 == 'E' || // HEAD
134
+      magic1 == 'O' && magic2 == 'P' || // OPTIONS
135
+      magic1 == 'P' && magic2 == 'A' || // PATCH
136
+      magic1 == 'D' && magic2 == 'E' || // DELETE
137
+      magic1 == 'T' && magic2 == 'R' || // TRACE
138
+      magic1 == 'C' && magic2 == 'O'    // CONNECT
139
+  }
140
+
141
+}
142
+
143
+class TcpIndexHandler(indexer: Indexer) extends ChannelInboundMessageHandlerAdapter[String] {
144
+  var server: String = null
145
+  var channel : String = null
146
+  var botName : String = null
147
+  var inited = false
148
+  val parser = new CSVParser
149
+
150
+  override def messageReceived(ctx: ChannelHandlerContext, content : String) {
151
+    val values = parser.parseLine(content)
152
+    if (!inited) {
153
+      assert(values.length == 3, "Server, channel and botName should be provided first")
154
+      server = values(0)
155
+      channel = values(1)
156
+      botName = values(2)
157
+      inited = true
158
+    } else {
159
+      indexer.index(new IndexRequest(server, channel, botName,
160
+          List(ChatLine(values(0), values(1).toLong, values(2)))))
161
+    }
162
+  }
163
+}
164
+
91 165
 @Sharable
92 166
 object EchoHandler extends HttpRequestHandler {
93 167
   override def messageReceived(ctx: ChannelHandlerContext, request: HttpRequest) {
@@ -97,9 +171,8 @@ object EchoHandler extends HttpRequestHandler {
97 171
 }
98 172
 
99 173
 @Sharable
100
-object IndexHandler extends HttpRequestHandler {
174
+class IndexHandler(indexer: Indexer) extends HttpRequestHandler {
101 175
   implicit val formats = DefaultFormats
102
-  lazy val indexer = { val indexer = new Indexer; indexer.start; indexer }
103 176
   override def messageReceived(ctx: ChannelHandlerContext, request: HttpRequest) {
104 177
     future {
105 178
       val content = request.getContent().toString(Charset.forName("UTF-8"))
@@ -108,15 +181,27 @@ object IndexHandler extends HttpRequestHandler {
108 181
     }
109 182
     logRequest(ctx, request, sendDefaultResponse(ctx, request))
110 183
   }
111
-  def stop = indexer.stop
112 184
 }
113 185
 
114 186
 @Sharable
115 187
 object SearchHandler extends HttpRequestHandler {
116 188
   implicit val formats = DefaultFormats
117 189
   override def messageReceived(ctx: ChannelHandlerContext, request: HttpRequest) {
118
-    val content = request.getContent().toString(Charset.forName("UTF-8"))
119
-    val searchRequest = Serialization.read[SearchRequest](content)
190
+    val method = request.getMethod()
191
+    val searchRequest = if (HttpMethod.POST.equals(method)) {
192
+      val content = request.getContent().toString(Charset.forName("UTF-8"))
193
+      Serialization.read[SearchRequest](content)
194
+    } else if (HttpMethod.GET.equals(method)) {
195
+      val params = new QueryStringDecoder(request.getUri).getParameters
196
+      val server = params("server")(0)
197
+      val channel = params("channel")(0)
198
+      val botName = params("botName")(0)
199
+      val query = params("query")(0)
200
+      new SearchRequest(server, channel, botName, query)
201
+    } else {
202
+      throw new UnsupportedOperationException("HTTP method " + method + " is not supported")
203
+    }
204
+
120 205
     val searchResult = Searcher.search(searchRequest)
121 206
     logRequest(ctx, request, sendSuccess(ctx, request, Serialization.write(searchResult)))
122 207
   }

+ 13
- 22
src/main/scala/net/abhinavsarkar/ircsearch/lucene/Indexer.scala View File

@@ -16,18 +16,17 @@ import org.apache.lucene.analysis.en.EnglishAnalyzer
16 16
 import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper
17 17
 import org.apache.lucene.analysis.standard.StandardAnalyzer
18 18
 import org.apache.lucene.document.Field
19
-import org.apache.lucene.document.FieldType
20
-import org.apache.lucene.document.FieldType.NumericType
21
-import org.apache.lucene.index.IndexReader
19
+import org.apache.lucene.document.LongField
20
+import org.apache.lucene.document.StringField
21
+import org.apache.lucene.document.TextField
22 22
 import org.apache.lucene.index.IndexWriter
23 23
 import org.apache.lucene.index.IndexWriterConfig
24
-import org.apache.lucene.search.IndexSearcher
25 24
 import org.apache.lucene.store.FSDirectory
26 25
 import org.apache.lucene.util.Version
27 26
 
28 27
 import com.typesafe.scalalogging.slf4j.Logging
29 28
 
30
-import net.abhinavsarkar.ircsearch.model.IndexRequest
29
+import net.abhinavsarkar.ircsearch.model._
31 30
 
32 31
 class Indexer extends Logging {
33 32
 
@@ -47,7 +46,9 @@ class Indexer extends Logging {
47 46
         def run {
48 47
           try {
49 48
             runLock.lock
50
-            logger.debug("Running indexer")
49
+            if (indexQueue.isEmpty)
50
+              return
51
+
51 52
             val indexReqs = new ArrayList[IndexRequest]
52 53
             indexQueue.drainTo(indexReqs)
53 54
             doIndex(indexReqs.toList)
@@ -57,7 +58,7 @@ class Indexer extends Logging {
57 58
             runLock.unlock
58 59
           }
59 60
         }},
60
-      0, 10, TimeUnit.SECONDS)
61
+      0, 1, TimeUnit.SECONDS)
61 62
   }
62 63
 
63 64
   def stop {
@@ -85,9 +86,9 @@ class Indexer extends Logging {
85 86
       try {
86 87
         for (indexRequest <- indexRequestBatch;
87 88
              chatLine     <- indexRequest.chatLines) {
88
-          val tsField = mkField("timestamp", chatLine.timestamp.toString, false)
89
-          val userField = mkField("user", chatLine.user, true)
90
-          val msgField = mkField("message", chatLine.message)
89
+          val tsField = new LongField(ChatLine.TS, chatLine.timestamp, Field.Store.YES)
90
+          val userField = new StringField(ChatLine.USER, chatLine.user, Field.Store.YES)
91
+          val msgField = new TextField(ChatLine.MSG, chatLine.message, Field.Store.YES)
91 92
           indexWriter.addDocument(List(tsField, userField, msgField), analyzer)
92 93
           logger.debug("Indexed : [{} {} {}] [{}] {}: {}",
93 94
               server, channel, botName, chatLine.timestamp.toString, chatLine.user, chatLine.message)
@@ -108,8 +109,8 @@ object Indexer {
108 109
   def mkAnalyzer : Analyzer = {
109 110
     val defAnalyzer = new StandardAnalyzer(LUCENE_VERSION)
110 111
     val fieldAnalyzers = Map(
111
-        "user" -> new KeywordAnalyzer,
112
-        "message" -> new EnglishAnalyzer(LUCENE_VERSION))
112
+        ChatLine.USER -> new KeywordAnalyzer,
113
+        ChatLine.MSG -> new EnglishAnalyzer(LUCENE_VERSION))
113 114
 
114 115
     new PerFieldAnalyzerWrapper(defAnalyzer, fieldAnalyzers)
115 116
   }
@@ -125,14 +126,4 @@ object Indexer {
125 126
   def getIndexDir(server : String, channel : String, botName : String) : String =
126 127
     s"index-$server-$channel-$botName"
127 128
 
128
-  private def mkField(name : String, value : String,
129
-      tokenized : Boolean = true, numericType : Option[NumericType] = None) : Field = {
130
-    val fieldType = new FieldType
131
-    fieldType.setStored(true)
132
-    fieldType.setIndexed(true)
133
-    fieldType.setTokenized(tokenized)
134
-    numericType.foreach { fieldType.setNumericType }
135
-    new Field(name, value, fieldType)
136
-  }
137
-
138 129
 }

+ 29
- 28
src/main/scala/net/abhinavsarkar/ircsearch/lucene/Searcher.scala View File

@@ -1,29 +1,27 @@
1 1
 package net.abhinavsarkar.ircsearch.lucene
2 2
 
3
-import com.typesafe.scalalogging.slf4j.Logging
4
-import org.apache.lucene.search.IndexSearcher
5 3
 import java.io.File
6
-import org.apache.lucene.index.IndexReader
7
-import org.apache.lucene.store.FSDirectory
4
+
5
+import scala.collection.JavaConversions._
6
+import scala.collection.mutable
7
+
8 8
 import org.apache.lucene.analysis.Analyzer
9
+import org.apache.lucene.index.IndexReader
9 10
 import org.apache.lucene.queryparser.classic.QueryParser
10
-import org.apache.lucene.search.Query
11
-import scala.collection.immutable.Set
12
-import org.apache.lucene.search.BooleanQuery
13
-import org.apache.lucene.search.TermQuery
14 11
 import org.apache.lucene.search.BooleanClause
12
+import org.apache.lucene.search.BooleanQuery
13
+import org.apache.lucene.search.FilteredQuery
14
+import org.apache.lucene.search.IndexSearcher
15
+import org.apache.lucene.search.Query
15 16
 import org.apache.lucene.search.QueryWrapperFilter
16
-import org.apache.lucene.search.Filter
17
-import net.abhinavsarkar.ircsearch.model.SearchRequest
18
-import net.abhinavsarkar.ircsearch.model.SearchResult
19 17
 import org.apache.lucene.search.Sort
20 18
 import org.apache.lucene.search.SortField
21
-import scala.collection.JavaConversions._
22
-import scala.collection.mutable
23
-import net.abhinavsarkar.ircsearch.model.ChatLine
24
-import net.abhinavsarkar.ircsearch.model.ChatLine
25
-import net.abhinavsarkar.ircsearch.model.SearchResult
26
-import net.abhinavsarkar.ircsearch.model.SearchResult
19
+import org.apache.lucene.search.TermQuery
20
+import org.apache.lucene.store.FSDirectory
21
+
22
+import com.typesafe.scalalogging.slf4j.Logging
23
+
24
+import net.abhinavsarkar.ircsearch.model._
27 25
 
28 26
 object Searcher extends Logging {
29 27
 
@@ -35,9 +33,9 @@ object Searcher extends Logging {
35 33
   }
36 34
 
37 35
   private def mkQueryParser(analyzer : Analyzer) =
38
-    new QueryParser(Indexer.LUCENE_VERSION, "message", analyzer)
36
+    new QueryParser(Indexer.LUCENE_VERSION, ChatLine.MSG, analyzer)
39 37
 
40
-  private def filterifyQuery(query : Query, mustFields : Set[String]) : (Query, Option[Filter]) =
38
+  private def filterifyQuery(query : Query, mustFields : Set[String]) : Query =
41 39
     query match {
42 40
       case boolQuery: BooleanQuery => {
43 41
         val newQuery = new BooleanQuery
@@ -58,9 +56,12 @@ object Searcher extends Logging {
58 56
           }
59 57
         }
60 58
 
61
-        (newQuery, if (filterQuery.clauses.isEmpty) None else Some(new QueryWrapperFilter(filterQuery)))
59
+        if (filterQuery.clauses.isEmpty)
60
+          newQuery
61
+        else
62
+          new FilteredQuery(newQuery, new QueryWrapperFilter(filterQuery))
62 63
       }
63
-      case _ => (query, None)
64
+      case _ => query
64 65
     }
65 66
 
66 67
   def search(searchRequest : SearchRequest) : SearchResult = {
@@ -71,9 +72,9 @@ object Searcher extends Logging {
71 72
     val analyzer = Indexer.mkAnalyzer
72 73
     try {
73 74
       val queryParser = mkQueryParser(analyzer)
74
-      val (query, filter) = filterifyQuery(queryParser.parse(searchRequest.query), Set("user"))
75
-      logger.debug("Query: {}, Filter: {}", query, filter)
76
-      val (totalResults, results) = doSearch(indexDir, query, filter, searchRequest.pageSize)
75
+      val query = filterifyQuery(queryParser.parse(searchRequest.query), Set(ChatLine.USER))
76
+      logger.debug("Query: {}", query)
77
+      val (totalResults, results) = doSearch(indexDir, query, searchRequest.pageSize)
77 78
       val searchResults = SearchResult.fromSearchRequest(searchRequest)
78 79
         .copy(totalResults = totalResults, chatLines = results.map(_._1))
79 80
       logger.debug("Search results: {}", searchResults)
@@ -83,18 +84,18 @@ object Searcher extends Logging {
83 84
     }
84 85
   }
85 86
 
86
-  private def doSearch(indexDir : String, query : Query, filter : Option[Filter], maxHits : Int)
87
+  private def doSearch(indexDir : String, query : Query, maxHits : Int)
87 88
     : (Int, List[(ChatLine, Float)]) = {
88 89
     val indexSearcher = mkIndexSearcher(indexDir)
89
-    val topDocs = indexSearcher.search(query, filter.orNull, maxHits,
90
-        new Sort(SortField.FIELD_SCORE, new SortField("timestamp", SortField.Type.LONG, true)))
90
+    val topDocs = indexSearcher.search(query, maxHits,
91
+        new Sort(SortField.FIELD_SCORE, new SortField(ChatLine.TS, SortField.Type.LONG, true)))
91 92
     val docs = topDocs.scoreDocs.map { sd =>
92 93
       val score = sd.score
93 94
       val doc = indexSearcher.doc(sd.doc).getFields.foldLeft(mutable.Map[String, String]()) {
94 95
         (map, field) => map += (field.name -> field.stringValue)
95 96
       }
96 97
 
97
-      val chatLine = new ChatLine(doc("user"), doc("timestamp").toLong, doc("message"))
98
+      val chatLine = new ChatLine(doc(ChatLine.USER), doc(ChatLine.TS).toLong, doc(ChatLine.MSG))
98 99
       (chatLine, score)
99 100
     }
100 101
     (topDocs.totalHits, docs.toList)

+ 6
- 0
src/main/scala/net/abhinavsarkar/ircsearch/model.scala View File

@@ -1,6 +1,12 @@
1 1
 package net.abhinavsarkar.ircsearch.model
2 2
 
3 3
 
4
+object ChatLine {
5
+  val USER = "user"
6
+  val TS = "ts"
7
+  val MSG = "msg"
8
+}
9
+
4 10
 case class ChatLine(user : String, timestamp : Long, message : String)
5 11
 
6 12
 case class IndexRequest(

Loading…
Cancel
Save