Support tokens in match information (fixes #570)

Change-Id: If5d4289eebad0ee6ed71079ecebe01b2412be554
diff --git a/core/Changes b/core/Changes
index 00a5230..55f4ec9 100644
--- a/core/Changes
+++ b/core/Changes
@@ -1,5 +1,7 @@
 # version 0.69.4
 
+- Support token array in matchinfo (fixes #570; diewald)
+
 # version 0.69.3
 
 # version 0.69.2
diff --git a/core/pom.xml b/core/pom.xml
index b6b6062..ea60346 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -14,7 +14,7 @@
 		<hibernate.version>5.6.15.Final</hibernate.version>
 		<flyway.version>9.16.3</flyway.version>
 		<log4j.version>2.19.0</log4j.version>
-		<krill.version>[0.61.1,)</krill.version>
+		<krill.version>[0.61.2,)</krill.version>
 		<koral.version>[0.39,)</koral.version>
 	</properties>
 	
diff --git a/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java b/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
index da1924a..f1b70ef 100644
--- a/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
+++ b/core/src/main/java/de/ids_mannheim/korap/service/SearchService.java
@@ -403,13 +403,17 @@
         return p;
     }
     
-    public String retrieveMatchInfo (String corpusId, String docId,
-            String textId, String matchId, Set<String> foundries,
-            String username, HttpHeaders headers, Set<String> layers,
-            boolean spans, boolean sentenceExpansion,
-            boolean highlights) throws KustvaktException {
+    public String retrieveMatchInfo (
+        String corpusId, String docId,
+        String textId, String matchId, boolean info, Set<String> foundries,
+        String username, HttpHeaders headers, Set<String> layers,
+        boolean spans,
+        boolean snippet, boolean tokens,
+        boolean sentenceExpansion,
+        boolean highlights
+        ) throws KustvaktException {
         String matchid =
-                searchKrill.getMatchId(corpusId, docId, textId, matchId);
+            searchKrill.getMatchId(corpusId, docId, textId, matchId);
 
         User user = createUser(username, headers);
         Pattern p = determineAvailabilityPattern(user);
@@ -417,28 +421,34 @@
         boolean match_only = foundries == null || foundries.isEmpty();
         String results;
 //        try {
-            if (!match_only) {
 
-                ArrayList<String> foundryList = new ArrayList<String>();
-                ArrayList<String> layerList = new ArrayList<String>();
+        ArrayList<String> foundryList = null;
+        ArrayList<String> layerList = null;
 
-                // EM: now without user, just list all foundries and
-                // layers
-                if (foundries.contains("*")) {
-                    foundryList = config.getFoundries();
-                    layerList = config.getLayers();
-                }
-                else {
-                    foundryList.addAll(foundries);
-                    layerList.addAll(layers);
-                }
-
-                results = searchKrill.getMatch(matchid, foundryList, layerList,
-                        spans, highlights, sentenceExpansion, p);
+        if (foundries != null && !foundries.isEmpty()) {
+            foundryList = new ArrayList<String>();
+            layerList = new ArrayList<String>();
+            // EM: now without user, just list all foundries and
+            // layers
+            if (foundries.contains("*")) {
+                foundryList = config.getFoundries();
+                layerList = config.getLayers();
             }
             else {
-                results = searchKrill.getMatch(matchid, p);
+                foundryList.addAll(foundries);
+                layerList.addAll(layers);
             }
+        } else {
+            sentenceExpansion = false;
+            spans = false;
+            info = false;
+            highlights = true;
+        };
+        
+        results = searchKrill.getMatch(
+            matchid, info, foundryList, layerList,
+            spans, snippet, tokens, highlights,
+            sentenceExpansion, p);
 //        }
 //        catch (Exception e) {
 //            jlog.error("Exception in the MatchInfo service encountered!", e);
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java b/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java
index 2eb98ab..6d6f52b 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/SearchKrill.java
@@ -209,15 +209,18 @@
     };
 
 
-    public String getMatch (String id, List<String> foundries,
-            List<String> layers, boolean includeSpans,
-            boolean includeHighlights, boolean sentenceExpansion,
-            Pattern licensePattern) throws KustvaktException {
+    public String getMatch (
+        String id, boolean info, List<String> foundries,
+        List<String> layers, boolean includeSpans,
+        boolean includeSnippet, boolean includeTokens,
+        boolean includeHighlights, boolean sentenceExpansion,
+        Pattern licensePattern) throws KustvaktException {
         Match km;
         if (index != null) {
             try {
-                km = index.getMatchInfo(id, "tokens", true, foundries, layers,
-                        includeSpans, includeHighlights, sentenceExpansion);
+                km = index.getMatchInfo(id, "tokens", info, foundries, layers,
+                                        includeSpans, includeSnippet, includeTokens,
+                                        includeHighlights, sentenceExpansion);
                 String availability = km.getAvailability();
                 checkAvailability(licensePattern, availability, id);
             }
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java b/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
index 18775b2..3cc768b 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
@@ -16,6 +16,7 @@
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
@@ -263,7 +264,8 @@
             @QueryParam("hls") Boolean highlights) throws KustvaktException {
 
         return retrieveMatchInfo(ctx, headers, locale, corpusId, docId, textId,
-                                 matchId, foundries, layers, spans, "sentence", highlights);
+                                 matchId, foundries, layers, spans, "true", "false",
+                                 "sentence", highlights);
     }
     
     @GET
@@ -278,6 +280,8 @@
             @QueryParam("foundry") Set<String> foundries,
             @QueryParam("layer") Set<String> layers,
             @QueryParam("spans") Boolean spans, 
+            @DefaultValue("true") @QueryParam("show-snippet") String snippetStr, 
+            @DefaultValue("false") @QueryParam("show-tokens") String tokensStr, 
             @QueryParam("expand") String expansion, 
             // Highlights may also be a list of valid highlight classes
             @QueryParam("hls") Boolean highlights) throws KustvaktException {
@@ -290,13 +294,23 @@
         TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
         scopeService.verifyScope(tokenContext, OAuth2Scope.MATCH_INFO);
         spans = spans != null ? spans : false;
+        Boolean snippet = true;
+        Boolean tokens = false;
+        if (snippetStr != null && (snippetStr.equals("false") || snippetStr.equals("null")))
+            snippet = false;
+
+        if (tokensStr != null && (tokensStr.equals("true") || tokensStr.equals("1") || tokensStr.equals("yes")))
+            tokens = true;
+
         highlights = highlights != null ? highlights : false;
         if (layers == null || layers.isEmpty()) layers = new HashSet<>();
 
         try{
-            String results = searchService.retrieveMatchInfo(corpusId, docId,
-                    textId, matchId, foundries, tokenContext.getUsername(),
-                    headers, layers, spans, expandToSentence, highlights);
+            String results = searchService.retrieveMatchInfo(
+                corpusId, docId,
+                textId, matchId, true, foundries, tokenContext.getUsername(),
+                headers, layers, spans, snippet, tokens,
+                expandToSentence, highlights);
             return Response.ok(results).build();
         }
         catch (KustvaktException e) {
diff --git a/full/Changes b/full/Changes
index 0390e7a..e9ffeaa 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,5 +1,6 @@
 # version 0.69.4
 
+- Support token array in matchinfo (fixes #570; diewald)
 
 # version 0.69.3
 
diff --git a/lite/Changes b/lite/Changes
index 7808c66..9168262 100644
--- a/lite/Changes
+++ b/lite/Changes
@@ -1,5 +1,7 @@
 # version 0.69.4
 
+- Support token array in matchinfo (fixes #570; diewald)
+
 # version 0.69.3
 
 # version 0.69.2
diff --git a/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchControllerTest.java b/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchControllerTest.java
index 9378ed8..f120ff2 100644
--- a/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchControllerTest.java
+++ b/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchControllerTest.java
@@ -347,6 +347,48 @@
     }
 
     @Test
+    public void testTokenRetrieval () throws KustvaktException {
+        Response response =
+                target().path(API_VERSION).path("/corpus/GOE/AGA/01784/p104-105/")
+                        .request()
+                        .method("GET");
+        assertEquals(Status.OK.getStatusCode(),
+                response.getStatus());
+        String resp = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(resp);
+        assertTrue(node.at("/hasSnippet").asBoolean());
+        assertFalse(node.at("/hasTokens").asBoolean());
+        assertTrue(node.at("/tokens").isMissingNode());
+        assertEquals(
+            "<span class=\"context-left\"><span class=\"more\"></span></span>"+
+            "<span class=\"match\"><mark>die</mark></span>"+
+            "<span class=\"context-right\"><span class=\"more\"></span></span>",
+            node.at("/snippet").asText());
+        
+        // Tokens
+        response =
+             target().path(API_VERSION).path("/corpus/GOE/AGA/01784/p104-105")
+             .queryParam("show-snippet", "false")
+             .queryParam("show-tokens", "true")
+             .queryParam("expand", "false")
+             .request()
+             .method("GET");
+        assertEquals(Status.OK.getStatusCode(),
+                response.getStatus());
+        resp = response.readEntity(String.class);
+        node = JsonUtils.readTree(resp);
+
+        assertFalse(node.at("/hasSnippet").asBoolean());
+        assertTrue(node.at("/hasTokens").asBoolean());
+        assertTrue(node.at("/snippet").isMissingNode());
+        assertEquals(
+            "die",
+            node.at("/tokens/match/0").asText());
+        assertTrue(node.at("/tokens/match/1").isMissingNode());
+    };
+
+    
+    @Test
     public void testMetaFields () throws KustvaktException {
         Response response =
                 target().path(API_VERSION).path("/corpus/GOE/AGA/01784")