Added maximum number of user persistent virtual corpora and queries

Change-Id: I6bbf17ed9cc68203908208c0bc46f81e55fe0d8a
diff --git a/full/Changes b/full/Changes
index 5ca29e5..8c18899 100644
--- a/full/Changes
+++ b/full/Changes
@@ -6,7 +6,11 @@
   when it has been included in the authorization request.
 - Added tests for VC sharing and for OAuth2 client using VC services
 - Added and updated VC controller tests
+<<<<<<< Upstream, based on master
 - Moved hibernate.properties
+=======
+- Added maximum number of user persistent virtual corpora and queries
+>>>>>>> 4db5bf8 Added maximum number of user persistent virtual corpora and queries
 
 # version 0.69.4
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
index 77108d8..175202a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
@@ -82,7 +82,9 @@
     private int refreshTokenLongExpiry;
     private int refreshTokenExpiry;
     private int authorizationCodeExpiry;
-
+    
+    private int maxNumberOfUserQueries;
+    
     private URL issuer;
     private URI issuerURI;
     private OpenIdConfiguration openidConfig;
@@ -129,13 +131,15 @@
         config.setMaxBytesLocalDisk(properties.getProperty("cache.max.bytes.local.disk", "2G"));
         jlog.info("max local heap:"+config.getMaxBytesLocalHeapAsString());
         jlog.info("max local disk:"+config.getMaxBytesLocalDiskAsString());
+        
+        setMaxNumberOfUserQueries(Integer.parseInt(
+                properties.getProperty("max.user.persistent.queries", "20")));
     }
 
     private void setSecurityConfiguration (Properties properties) {
         setSecureHashAlgorithm(Enum.valueOf(EncryptionIface.Encryption.class,
                 properties.getProperty("security.secure.hash.algorithm",
                         "BCRYPT")));
-
     }
 
     private void setOpenIdConfiguration (Properties properties)
@@ -685,6 +689,13 @@
     public void setCreateInitialSuperClient (boolean initialSuperClient) {
         this.createInitialSuperClient = initialSuperClient;
     }
-    
+
+    public int getMaxNumberOfUserQueries () {
+        return maxNumberOfUserQueries;
+    }
+
+    public void setMaxNumberOfUserQueries (int maxNumberOfUserQueries) {
+        this.maxNumberOfUserQueries = maxNumberOfUserQueries;
+    }
     
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java
index c8ae459..50d5b98 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java
@@ -11,6 +11,7 @@
 import javax.persistence.NonUniqueResultException;
 import javax.persistence.PersistenceContext;
 import javax.persistence.Query;
+import javax.persistence.TypedQuery;
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.Join;
@@ -21,18 +22,18 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.QueryAccessStatus;
 import de.ids_mannheim.korap.constant.QueryType;
 import de.ids_mannheim.korap.constant.ResourceType;
 import de.ids_mannheim.korap.constant.UserGroupStatus;
-import de.ids_mannheim.korap.constant.QueryAccessStatus;
+import de.ids_mannheim.korap.entity.QueryAccess;
+import de.ids_mannheim.korap.entity.QueryAccess_;
 import de.ids_mannheim.korap.entity.QueryDO;
 import de.ids_mannheim.korap.entity.QueryDO_;
 import de.ids_mannheim.korap.entity.UserGroup;
 import de.ids_mannheim.korap.entity.UserGroupMember;
 import de.ids_mannheim.korap.entity.UserGroupMember_;
 import de.ids_mannheim.korap.entity.UserGroup_;
-import de.ids_mannheim.korap.entity.QueryAccess;
-import de.ids_mannheim.korap.entity.QueryAccess_;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.user.User.CorpusAccess;
@@ -375,4 +376,24 @@
         return q.getResultList();
     }
 
+    public Long countNumberOfQuery (String userId, QueryType queryType)
+            throws KustvaktException {
+        ParameterChecker.checkStringValue(userId, "userId");
+        ParameterChecker.checkObjectValue(queryType, "queryType");
+
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<Long> cq = builder.createQuery(Long.class);
+
+        Root<QueryDO> query = cq.from(QueryDO.class);
+        Predicate conditions = builder.and(
+                builder.equal(query.get(QueryDO_.createdBy), userId),
+                builder.equal(query.get(QueryDO_.queryType), queryType));
+
+        cq.select(builder.count(query));
+        cq.where(conditions);
+
+        TypedQuery<Long> q = entityManager.createQuery(cq);
+        return q.getSingleResult();
+    }
+
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/QueryService.java b/full/src/main/java/de/ids_mannheim/korap/service/QueryService.java
index 419f374..8b339ed 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/QueryService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/QueryService.java
@@ -267,25 +267,45 @@
 
     public void storeQuery (QueryJson query, String queryName,
             String queryCreator, String username) throws KustvaktException {
-        String koralQuery = null;
-        if (query.getQueryType().equals(QueryType.VIRTUAL_CORPUS)) {
-            ParameterChecker.checkStringValue(query.getCorpusQuery(),
-                    "corpusQuery");
-            koralQuery = serializeCorpusQuery(query.getCorpusQuery());
+        QueryType queryType = query.getQueryType();
+        if (!checkNumberOfQueryLimit(username, queryType)) {
+            String type = queryType.displayName().toLowerCase();
+            throw new KustvaktException(StatusCodes.NOT_ALLOWED, 
+                    "Cannot create "+type+". The maximum number "
+                            + "of "+type+" has been reached.");
         }
-        else if (query.getQueryType().equals(QueryType.QUERY)) {
-            ParameterChecker.checkStringValue(query.getQuery(), "query");
-            ParameterChecker.checkStringValue(query.getQueryLanguage(),
-                    "queryLanguage");
-            koralQuery =
-                    serializeQuery(query.getQuery(), query.getQueryLanguage());
-        }
-
+        
+        String koralQuery = computeKoralQuery(query);
         storeQuery(username, queryName, query.getType(), query.getQueryType(),
                 koralQuery, query.getDefinition(), query.getDescription(),
                 query.getStatus(), query.isCached(), queryCreator,
                 query.getQuery(), query.getQueryLanguage());
     }
+    
+    private boolean checkNumberOfQueryLimit (String username,
+            QueryType queryType) throws KustvaktException {
+        Long num = queryDao.countNumberOfQuery(username, queryType);
+        if (num < config.getMaxNumberOfUserQueries()) return true;
+        else return false;
+    }
+    
+    private String computeKoralQuery (QueryJson query) throws KustvaktException {
+        if (query.getQueryType().equals(QueryType.VIRTUAL_CORPUS)) {
+            ParameterChecker.checkStringValue(query.getCorpusQuery(),
+                    "corpusQuery");
+            return serializeCorpusQuery(query.getCorpusQuery());
+        }
+        
+        if (query.getQueryType().equals(QueryType.QUERY)) {
+            ParameterChecker.checkStringValue(query.getQuery(), "query");
+            ParameterChecker.checkStringValue(query.getQueryLanguage(),
+                    "queryLanguage");
+            return
+                    serializeQuery(query.getQuery(), query.getQueryLanguage());
+        }
+        
+        return null;
+    }
 
     public void storeQuery (String username, String queryName,
             ResourceType type, QueryType queryType, String koralQuery,
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
index 3c67974..4106a7a 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
@@ -381,6 +381,47 @@
                 "Cannot deserialize value of type `de.ids_mannheim.korap.constant."
                         + "ResourceType` from String \"PRIVAT\""));
     }
+    
+    @Test
+    public void testMaxNumberOfVC () throws KustvaktException {
+        String json = "{\"type\": \"PRIVATE\""
+                + ",\"queryType\": \"VIRTUAL_CORPUS\""
+                + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+
+        for (int i=1; i<6; i++) {
+            createVC(authHeader,testUser, "new_vc_"+i, json);
+        }
+
+        Response response = target().path(API_VERSION).path("vc")
+                .path("~" + testUser).path("new_vc_6").request()
+                .header(Attributes.AUTHORIZATION, authHeader)
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .put(Entity.json(json));
+
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        
+        assertEquals(StatusCodes.NOT_ALLOWED,
+                node.at("/errors/0/0").asInt());
+        assertEquals("Cannot create virtual corpus. The maximum number of "
+                + "virtual corpus has been reached.",
+                node.at("/errors/0/1").asText());
+
+        // list user VC
+        node = listVC(testUser);
+        assertEquals(6, node.size()); // including 1 system-vc
+
+        // delete new VC
+        for (int i=1; i<6; i++) {
+            deleteVC("new_vc_"+i, testUser, testUser);
+        }
+
+        // list VC
+        node = listVC(testUser);
+        assertEquals(1, node.size()); // system-vc
+    }
 
     @Test
     public void testDeleteVC_unauthorized () throws KustvaktException {
diff --git a/full/src/test/resources/kustvakt-test.conf b/full/src/test/resources/kustvakt-test.conf
index 430ad5f..893d04b 100644
--- a/full/src/test/resources/kustvakt-test.conf
+++ b/full/src/test/resources/kustvakt-test.conf
@@ -52,6 +52,9 @@
 delete.group = soft
 delete.group.member = soft
 
+# Virtual corpus and queries
+max.user.persistent.queries = 5
+
 # Availability regex only support |
 # It should be removed/commented when the data doesn't contain availability field.
 #