Merge "Added show-tokens option to the search API."
diff --git a/core/Changes b/core/Changes
index 771855c..056aed2 100644
--- a/core/Changes
+++ b/core/Changes
@@ -10,7 +10,8 @@
  - [security] More log4j security updates
  - Cleaned up LDAP libraries
  - Bumped spring.version from 5.3.13 to 5.3.14
-
+2022-01-25
+ - Added show-tokens option to the search API.
 
 # version 0.64
 
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 1e30a9e..b1b5575 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
@@ -135,7 +135,8 @@
     public String search (String engine, String username, HttpHeaders headers,
             String q, String ql, String v, List<String> cqList, String fields,
             String pipes, Integer pageIndex, Integer pageInteger, String ctx,
-            Integer pageLength, Boolean cutoff, boolean accessRewriteDisabled)
+            Integer pageLength, Boolean cutoff, boolean accessRewriteDisabled,
+            boolean showTokens)
             throws KustvaktException {
 
         if (pageInteger != null && pageInteger < 1) {
@@ -168,7 +169,8 @@
         handleNonPublicFields(fieldList, accessRewriteDisabled, serializer);
         
         MetaQueryBuilder meta = createMetaQuery(pageIndex, pageInteger, ctx,
-                pageLength, cutoff, corpusAccess, fieldList, accessRewriteDisabled);
+                pageLength, cutoff, corpusAccess, fieldList, accessRewriteDisabled,
+                showTokens);
         serializer.setMeta(meta.raw());
         
         // There is an error in query processing
@@ -178,6 +180,15 @@
         }
 
         String query = serializer.toJSON();
+        
+        if (accessRewriteDisabled && showTokens) {
+            Notifications n = new Notifications();
+            n.addWarning(StatusCodes.NOT_ALLOWED,
+                    "Tokens cannot be shown without access.");
+            JsonNode warning = n.toJsonNode();
+            query = addWarning(query, warning);
+        }
+        
         query = runPipes(query,pipeArray);
         
         query = this.rewriteHandler.processQuery(query, user);
@@ -263,6 +274,14 @@
                 "Pipe failed", url, message);
         JsonNode warning = n.toJsonNode();
         
+        query = addWarning(query, warning);
+        return query; 
+    }
+    
+
+    private String addWarning (String query, JsonNode warning)
+            throws KustvaktException {
+        
         ObjectNode node = (ObjectNode) JsonUtils.readTree(query);
         if (node.has("warnings")){
             warning = warning.at("/warnings/0");
@@ -273,8 +292,7 @@
         else{
             node.setAll((ObjectNode) warning);
         }
-        
-        return node.toString(); 
+        return node.toString();
     }
 
     private void handleNonPublicFields (List<String> fieldList,
@@ -300,7 +318,8 @@
     private MetaQueryBuilder createMetaQuery (Integer pageIndex,
             Integer pageInteger, String ctx, Integer pageLength,
             Boolean cutoff, CorpusAccess corpusAccess, List<String> fieldList,
-            boolean accessRewriteDisabled) {
+            boolean accessRewriteDisabled,
+            boolean showTokens) {
         MetaQueryBuilder meta = new MetaQueryBuilder();
         meta.addEntry("startIndex", pageIndex);
         meta.addEntry("startPage", pageInteger);
@@ -309,6 +328,10 @@
         // todo: what happened to cutoff?
         meta.addEntry("cutOff", cutoff);
         meta.addEntry("snippets", !accessRewriteDisabled);
+        if (!accessRewriteDisabled) {
+            meta.addEntry("tokens", showTokens);
+        }
+        
         // meta.addMeta(pageIndex, pageInteger, pageLength, ctx,
         // cutoff);
         // fixme: should only apply to CQL queries per default!
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 dfab860..0f49d48 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
@@ -221,6 +221,7 @@
             @QueryParam("fields") String fields,
             @QueryParam("pipes") String pipes,
             @QueryParam("access-rewrite-disabled") boolean accessRewriteDisabled,
+            @QueryParam("show-tokens") boolean showTokens,
             @QueryParam("cq") List<String> cq, 
             @QueryParam("engine") String engine) {
 
@@ -233,7 +234,7 @@
             result = searchService.search(engine, context.getUsername(),
                     headers, q, ql, v, cq, fields, pipes, pageIndex,
                     pageInteger, ctx, pageLength, cutoff,
-                    accessRewriteDisabled);
+                    accessRewriteDisabled, showTokens);
         }
         catch (KustvaktException e) {
             throw kustvaktResponseHandler.throwit(e);
diff --git a/full/Changes b/full/Changes
index 02f043e..d84e29a 100644
--- a/full/Changes
+++ b/full/Changes
@@ -18,7 +18,8 @@
  - [security] More log4j security updates
  - Bumped unboundid-ldapsdk
  - Updated tests.
-
+2022-01-25
+ - Added show-tokens option to the search API.
 
 # version 0.64.1
 
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/PublicMetadataTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchPublicMetadataTest.java
similarity index 99%
rename from full/src/test/java/de/ids_mannheim/korap/web/controller/PublicMetadataTest.java
rename to full/src/test/java/de/ids_mannheim/korap/web/controller/SearchPublicMetadataTest.java
index d772eb2..3050ff6 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/PublicMetadataTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchPublicMetadataTest.java
@@ -17,7 +17,7 @@
 import de.ids_mannheim.korap.query.serialize.QuerySerializer;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
-public class PublicMetadataTest extends SpringJerseyTest {
+public class SearchPublicMetadataTest extends SpringJerseyTest {
 
     @Test
     public void testSearchPublicMetadata () throws KustvaktException {
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchTokenSnippetTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchTokenSnippetTest.java
new file mode 100644
index 0000000..c04f8a2
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchTokenSnippetTest.java
@@ -0,0 +1,78 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.jersey.api.client.ClientResponse;
+
+import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class SearchTokenSnippetTest extends SpringJerseyTest {
+
+    @Test
+    public void testSearchWithTokens () throws KustvaktException {
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+                .queryParam("show-tokens", "true")
+                .queryParam("context", "sentence").queryParam("count", "13")
+                .get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+        String ent = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+
+        assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+        assertTrue(node.at("/matches/0/hasTokens").asBoolean());
+        assertTrue(node.at("/matches/0/tokens/left").size()>0);
+        assertTrue(node.at("/matches/0/tokens/right").size()>0);
+        assertEquals(1, node.at("/matches/0/tokens/match").size());
+    }
+    
+    @Test
+    public void testSearchWithoutTokens () throws KustvaktException {
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+                .queryParam("show-tokens", "false")
+                .queryParam("context", "sentence").queryParam("count", "13")
+                .get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+        String ent = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+
+        assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+        assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+        assertTrue(node.at("/matches/0/tokens").isMissingNode());
+    }
+    
+    @Test
+    public void testSearchPublicMetadataWithTokens () throws KustvaktException {
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+                .queryParam("access-rewrite-disabled", "true")
+                .queryParam("show-tokens", "true")
+                .queryParam("context", "sentence").queryParam("count", "13")
+                .get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+        String ent = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+
+        assertFalse(node.at("/matches/0/hasSnippet").asBoolean());
+        assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+        assertTrue(node.at("/matches/0/snippet").isMissingNode());
+        assertTrue(node.at("/matches/0/tokens").isMissingNode());
+        
+        assertEquals(StatusCodes.NOT_ALLOWED, node.at("/warnings/0/0").asInt());
+    }
+}
diff --git a/lite/Changes b/lite/Changes
index 36cf15e..8f9fa21 100644
--- a/lite/Changes
+++ b/lite/Changes
@@ -8,7 +8,8 @@
 2022-01-03
  - [security] More log4j security updates
  - Updated tests.
-
+2022-01-25
+ - Added show-tokens option to the search API.
 
 # version 0.64
 
diff --git a/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchTokenSnippetTest.java b/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchTokenSnippetTest.java
new file mode 100644
index 0000000..feb6308
--- /dev/null
+++ b/lite/src/test/java/de/ids_mannheim/korap/web/service/LiteSearchTokenSnippetTest.java
@@ -0,0 +1,78 @@
+package de.ids_mannheim.korap.web.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.jersey.api.client.ClientResponse;
+
+import de.ids_mannheim.korap.config.LiteJerseyTest;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+public class LiteSearchTokenSnippetTest extends LiteJerseyTest{
+
+    @Test
+    public void testSearchWithTokens () throws KustvaktException {
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+                .queryParam("show-tokens", "true")
+                .queryParam("context", "sentence").queryParam("count", "13")
+                .get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+        String ent = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+
+        assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+        assertTrue(node.at("/matches/0/hasTokens").asBoolean());
+        assertTrue(node.at("/matches/0/tokens/left").size()>0);
+        assertTrue(node.at("/matches/0/tokens/right").size()>0);
+        assertEquals(1, node.at("/matches/0/tokens/match").size());
+    }
+    
+    @Test
+    public void testSearchWithoutTokens () throws KustvaktException {
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+                .queryParam("show-tokens", "false")
+                .queryParam("context", "sentence").queryParam("count", "13")
+                .get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+        String ent = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+
+        assertTrue(node.at("/matches/0/hasSnippet").asBoolean());
+        assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+        assertTrue(node.at("/matches/0/tokens").isMissingNode());
+    }
+    
+    @Test
+    public void testSearchPublicMetadataWithTokens () throws KustvaktException {
+        ClientResponse response = resource().path(API_VERSION).path("search")
+                .queryParam("q", "[orth=das]").queryParam("ql", "poliqarp")
+                .queryParam("access-rewrite-disabled", "true")
+                .queryParam("show-tokens", "true")
+                .queryParam("context", "sentence").queryParam("count", "13")
+                .get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+        String ent = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+
+        assertFalse(node.at("/matches/0/hasSnippet").asBoolean());
+        assertFalse(node.at("/matches/0/hasTokens").asBoolean());
+        assertTrue(node.at("/matches/0/snippet").isMissingNode());
+        assertTrue(node.at("/matches/0/tokens").isMissingNode());
+        
+        assertEquals(StatusCodes.NOT_ALLOWED, node.at("/warnings/0/0").asInt());
+    }
+}