Use database for statistics service

This function is limited to full version since lite doesn't support VC.
Since the statistics service accepts corpus query (cq) in general, it
only works for single VC reference.

Change-Id: I326f2de8a48dcd9387163aab8b05357233b47be4
diff --git a/src/main/java/de/ids_mannheim/korap/core/service/StatisticService.java b/src/main/java/de/ids_mannheim/korap/core/service/StatisticService.java
index 61b8cef..bacedb8 100644
--- a/src/main/java/de/ids_mannheim/korap/core/service/StatisticService.java
+++ b/src/main/java/de/ids_mannheim/korap/core/service/StatisticService.java
@@ -5,10 +5,15 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import de.ids_mannheim.korap.constant.QueryType;
+import de.ids_mannheim.korap.entity.QueryDO;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.rewrite.RewriteHandler;
+import de.ids_mannheim.korap.server.KustvaktBaseServer;
+import de.ids_mannheim.korap.service.QueryServiceInterface;
 import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.ServiceInfo;
 import jakarta.ws.rs.core.HttpHeaders;
 
 @Service
@@ -16,18 +21,35 @@
 
 	@Autowired
 	private RewriteHandler statisticsRewriteHandler;
+	@Autowired
+	private QueryServiceInterface queryService;
 	
 	public String retrieveStatisticsForCorpusQuery (List<String> cqList,
 			String username, HttpHeaders headers, double apiVersion) 
 					throws KustvaktException {
-
+		
+		// Check for single VC reference
+		if (!KustvaktBaseServer.kargs.isLite() && cqList.size() == 1) {
+			String cq = cqList.get(0);
+			if(cq.startsWith("referTo") && !cq.contains("&") && !cq.contains("|")) {
+				QueryDO vc = retrieveVCReference(cqList.get(0), username);
+    	        if (vc != null) {
+    	        	String vcStatistics = vc.getStatistics();
+    	        	if(vcStatistics != null && !vcStatistics.isEmpty()) {
+    	        		return vcStatistics;
+    	        	}
+    	        }
+			}
+		}
+		
 		String json = buildKoralQueryFromCorpusQuery(cqList, apiVersion);
-		//System.out.println("Before:" + json + "\n");
+//		System.out.println("Before:" + json + "\n");
 		if (!cqList.isEmpty() && !combineMultipleCorpusQuery(cqList).isEmpty()) {
 			User user = createUser(username, headers);
 			json = statisticsRewriteHandler.processQuery(json, user, apiVersion);
 		}
-		//System.out.println("After:" + json);
+//		System.out.println("After:" + json);
+		
 		String stats = searchKrill.getStatistics(json);
 
 		if (stats.contains("-1")) {
@@ -35,7 +57,31 @@
 		}
 		return stats;
 	}
-
+	
+	private QueryDO retrieveVCReference (String ref, String username) 
+			throws KustvaktException {
+		// expect something like: referTo "owner/vcName" or referTo "vcName"
+		String[] cqParts = ref.split(" ");
+		String vcOwner = "system";
+		String vcName = "";
+		if (cqParts.length > 1) {
+			vcName = cqParts[1];
+			// remove surrounding quotes, if any
+			if (vcName.startsWith("\"") && vcName.endsWith("\"")) {
+				vcName = vcName.substring(1, vcName.length() - 1);
+			}
+			if (vcName.contains("/")) {
+				String[] names = vcName.split("/");
+				if (names.length == 2) {
+					vcOwner = names[0]; // may not be "system"
+					vcName = names[1];
+				}
+			}
+		}
+		return queryService.searchQueryByName(username, vcName, vcOwner,
+				QueryType.VIRTUAL_CORPUS);
+	}
+	
     public String retrieveStatisticsForKoralQuery (String koralQuery, 
     		double apiVersion)
             throws KustvaktException {
@@ -60,4 +106,4 @@
     public String getIndexFingerprint () {
         return searchKrill.getIndexFingerprint();
     }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/service/DummyQueryServiceImpl.java b/src/main/java/de/ids_mannheim/korap/service/DummyQueryServiceImpl.java
new file mode 100644
index 0000000..3dad866
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/service/DummyQueryServiceImpl.java
@@ -0,0 +1,14 @@
+package de.ids_mannheim.korap.service;
+
+import de.ids_mannheim.korap.constant.QueryType;
+import de.ids_mannheim.korap.entity.QueryDO;
+
+public class DummyQueryServiceImpl implements QueryServiceInterface {
+
+	@Override
+	public QueryDO searchQueryByName(String username, String vcName,
+			String vcOwner, QueryType queryType) {
+		return null;
+	}
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/service/QueryService.java b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
index 143beb1..2382221 100644
--- a/src/main/java/de/ids_mannheim/korap/service/QueryService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
@@ -68,7 +68,7 @@
  *
  */
 @Service
-public class QueryService extends BasicService {
+public class QueryService extends BasicService implements QueryServiceInterface {
 
     public static Logger jlog = LogManager.getLogger(QueryService.class);
 
@@ -178,6 +178,29 @@
 			return null;
 		}
 	}
+	
+	private String computeStatisticsForVC (QueryType queryType, boolean isCached,
+			double apiVersion, String queryName, String koralQuery)
+			throws KustvaktException {
+        if (queryType.equals(QueryType.VIRTUAL_CORPUS)) {
+        	if (isCached) {
+                String kq = updateKoralQueryForCachedVC (apiVersion, queryName);
+                return krill.getStatistics(kq);
+            }
+        	else {
+        		return krill.getStatistics(koralQuery); 
+        	}
+        }
+        return null;
+	}
+    
+	private String updateKoralQueryForCachedVC (double apiVersion,
+			String queryName) throws KustvaktException {
+    	KoralCollectionQueryBuilder koral = 
+        		new KoralCollectionQueryBuilder(apiVersion);
+        koral.with("referTo " + queryName);
+        return koral.toJSON();
+	}
 
     public void deleteQueryByName (String deletedBy, String queryName,
             String createdBy, QueryType type) throws KustvaktException {
@@ -334,7 +357,7 @@
         }
 
         String koralQuery = computeKoralQuery(query, apiVersion);
-        storeQuery(username, queryName, query.getType(), query.getQueryType(),
+        storeQuery(null,username, queryName, query.getType(), query.getQueryType(),
                 koralQuery, query.getDefinition(), query.getDescription(),
                 query.getStatus(), query.isCached(), queryCreator,
                 query.getQuery(), query.getQueryLanguage(), apiVersion);
@@ -368,16 +391,6 @@
         return null;
     }
 
-    public void storeQuery (String username, String queryName,
-            ResourceType type, QueryType queryType, String koralQuery,
-            String definition, String description, String status,
-            boolean isCached, String queryCreator, String query,
-            String queryLanguage, double apiVersion) throws KustvaktException {
-        storeQuery(null, username, queryName, type, queryType, koralQuery,
-                definition, description, status, isCached, queryCreator, query,
-                queryLanguage, apiVersion);
-    }
-    
     public void storeQuery (QueryDO existingQuery, String username, String queryName,
             ResourceType type, QueryType queryType, String koralQuery,
             String definition, String description, String status,
@@ -418,6 +431,8 @@
             jlog.debug("Storing query: " + queryName + "in the database ");
         }
 
+        // EM: statistics is not updated for *non named-vc*, should we skip it 
+        // so statistics should always be computed on the fly for them?
 		String statistics = computeStatisticsForVC(queryType, isCached,
 				apiVersion, queryName, koralQuery);        
         int queryId = 0;
@@ -453,29 +468,6 @@
         }
     }
     
-	private String computeStatisticsForVC (QueryType queryType, boolean isCached,
-			double apiVersion, String queryName, String koralQuery)
-			throws KustvaktException {
-        if (queryType.equals(QueryType.VIRTUAL_CORPUS)) {
-        	if (isCached) {
-                String kq = updateKoralQueryForCachedVC (apiVersion, queryName);
-                return krill.getStatistics(kq);
-            }
-        	else {
-        		return krill.getStatistics(koralQuery); 
-        	}
-        }
-        return null;
-	}
-    
-	private String updateKoralQueryForCachedVC (double apiVersion,
-			String queryName) throws KustvaktException {
-    	KoralCollectionQueryBuilder koral = 
-        		new KoralCollectionQueryBuilder(apiVersion);
-        koral.with("referTo " + queryName);
-        return koral.toJSON();
-	}
-    
 	public void updateVCStatistics (QueryDO existingQuery, String statistics)
 			throws KustvaktException {
 		queryDao.editQuery(existingQuery, null, null, null, null, null, null,
@@ -768,9 +760,10 @@
         QueryDO query = searchQueryByName(username, queryName, createdBy,
                 queryType);
 
-        String statistics = null;
+        String statistics = query.getStatistics();
         String json = "";
-		if (query.getQueryType().equals(QueryType.VIRTUAL_CORPUS)) {
+		if (query.getQueryType().equals(QueryType.VIRTUAL_CORPUS) && 
+				(statistics == null || statistics.isEmpty())) {
 			if (query.isCached()) {
 				List<String> cqList = new ArrayList<>(1);
 				cqList.add("referTo " + query.getName());
diff --git a/src/main/java/de/ids_mannheim/korap/service/QueryServiceInterface.java b/src/main/java/de/ids_mannheim/korap/service/QueryServiceInterface.java
new file mode 100644
index 0000000..cc0c425
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/service/QueryServiceInterface.java
@@ -0,0 +1,25 @@
+package de.ids_mannheim.korap.service;
+
+import de.ids_mannheim.korap.constant.QueryType;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+
+/**
+ * Minimal interface exposing the query lookup used by other components.
+ */
+public interface QueryServiceInterface {
+
+    /**
+     * Search for a query (virtual corpus or stored query) by name and owner.
+     *
+     * @param username the user performing the lookup (for access checks)
+     * @param vcName   the query name (virtual corpus name)
+     * @param vcOwner  the owner/creator of the query (e.g. "system" or username)
+     * @param queryType the type of query to look up
+     * @return the matching QueryDO
+     * @throws KustvaktException on not found or authorization problems
+     */
+    QueryDO searchQueryByName(String username, String vcName,
+                              String vcOwner, QueryType queryType)
+            throws KustvaktException;
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
index 41d20a4..3a9cef0 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
@@ -60,6 +60,22 @@
         assertEquals(11,node.get("documents").asInt());
 	}
 
+    // EM: same results with login because availability rewrite is omitted 
+    // for statistics
+    @Test
+    public void testGetStatisticsProtectedVCWithoutLogin () throws KustvaktException {
+    	String corpusQuery = "availability=QAO-NC-LOC:ids";
+        Response response = target().path(API_VERSION).path("statistics")
+                .queryParam("cq", corpusQuery).request()
+                .get();
+        assert Status.OK.getStatusCode() == response.getStatus();
+        
+        String ent = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+        assertEquals(2,node.get("documents").asInt());
+        assertEquals(69056, node.at("/tokens").asInt());
+	}
+
     @Test
     public void testStatisticsWithCq () throws KustvaktException {
         Response response = target().path(API_VERSION).path("statistics")
@@ -75,6 +91,19 @@
         assertTrue(node.at("/warnings").isMissingNode());
     }
 
+    @Test
+    public void testGetStatisticsWithVC ()
+            throws IOException, KustvaktException {
+        String corpusQuery = "referTo system-vc";
+        Response response = target().path(API_VERSION).path("statistics")
+                .queryParam("cq", corpusQuery).request().get();
+        assert Status.OK.getStatusCode() == response.getStatus();
+        String ent = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(ent);
+        assertEquals(7,node.get("documents").asInt());
+        assertEquals(279402,node.get("tokens").asInt());
+    }
+
 
     @Test
     public void testGetStatisticsWithcorpusQuery1 ()
diff --git a/src/test/resources/test-config-lite.xml b/src/test/resources/test-config-lite.xml
index 0a3451d..ec689d9 100644
--- a/src/test/resources/test-config-lite.xml
+++ b/src/test/resources/test-config-lite.xml
@@ -184,6 +184,8 @@
 	<!-- Services -->
 	<bean id="scopeService"
 		class="de.ids_mannheim.korap.oauth2.service.DummyOAuth2ScopeServiceImpl" />
+	<bean id="queryService"
+		class="de.ids_mannheim.korap.service.DummyQueryServiceImpl" />
 
 	<!-- DAO -->
 	<bean id="adminDao"
diff --git a/src/test/resources/test-config.xml b/src/test/resources/test-config.xml
index 05311db..aad2bbe 100644
--- a/src/test/resources/test-config.xml
+++ b/src/test/resources/test-config.xml
@@ -238,8 +238,7 @@
 	</bean>
 	
 	<util:list id="statisticsRewriteTasks"
-		value-type="de.ids_mannheim.korap.rewrite.RewriteTask">
-		<ref bean="foundryRewrite" />
+		value-type="de.ids_mannheim.korap.rewrite.RewriteTask">		
 		<ref bean="timeoutRewrite" />
 		<ref bean="virtualCorpusRewrite" />
 		<ref bean="queryReferenceRewrite" />