Change VC and query names to lowercase (close #902)
Make VC and query names consistent by storing them in lowercase in the
database and cache.
Handle VC and query name incase-sensitivity in database access.
Handle referTo in VirtualCorpusRewrite
Change-Id: I7cb94e9b007b6a07fc8b9dd7dda4249318c6c3a5
diff --git a/Changes b/Changes
index 3e25a0e..2a90c92 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,6 @@
+# version 1.1-SNAPSHOT
+- Change VC and query names to lowercase
+
# version 1.0.1
- Add an exception for missing layer.
diff --git a/pom.xml b/pom.xml
index 9441e6b..954fab6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.ids-mannheim.korap.kustvakt</groupId>
<artifactId>Kustvakt</artifactId>
- <version>1.0.1</version>
+ <version>1.1-SNAPSHOT</version>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/src/main/java/de/ids_mannheim/korap/core/service/BasicService.java b/src/main/java/de/ids_mannheim/korap/core/service/BasicService.java
index 6ea75dc..129a392 100644
--- a/src/main/java/de/ids_mannheim/korap/core/service/BasicService.java
+++ b/src/main/java/de/ids_mannheim/korap/core/service/BasicService.java
@@ -81,6 +81,7 @@
}
}
+ vcName = vcName.toLowerCase();
String vcInCaching = config.getVcInCaching();
if (vcName.equals(vcInCaching)) {
throw new KustvaktException(
diff --git a/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java b/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java
index 93a75e9..3936550 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/QueryDao.java
@@ -201,7 +201,8 @@
Predicate condition = builder.and(
builder.equal(query.get(QueryDO_.createdBy), createdBy),
- builder.equal(query.get(QueryDO_.name), queryName));
+ builder.equal(query.get(QueryDO_.name),
+ queryName.toLowerCase()));
criteriaQuery.select(query);
criteriaQuery.where(condition);
@@ -364,4 +365,4 @@
return q.getSingleResult();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
index b2c09cf..9e0cb99 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
@@ -245,11 +245,12 @@
cb.equal(groupRole.get(UserGroup_.name), groupName),
cb.equal(queryRole.get(QueryDO_.createdBy),
queryCreator),
- cb.equal(queryRole.get(QueryDO_.name), queryName)));
+ cb.equal(queryRole.get(QueryDO_.name),
+ queryName.toLowerCase())));
delete.where(deleteRole.get(Role_.id).in(subquery));
entityManager.createQuery(delete).executeUpdate();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java b/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
index 8627f1d..af8742a 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
@@ -118,7 +118,6 @@
Root<UserGroupMember> memberRoot = cq.from(UserGroupMember.class);
cq.select(memberRoot);
cq.where(cb.equal(memberRoot.get(UserGroupMember_.group).get("id"), groupId));
- @SuppressWarnings("unchecked")
List<UserGroupMember> members = entityManager.createQuery(cq).getResultList();
for (UserGroupMember m : members) {
if (!entityManager.contains(m)) {
@@ -275,7 +274,8 @@
Predicate p = criteriaBuilder.and(
criteriaBuilder.equal(root.get(UserGroup_.status),
UserGroupStatus.HIDDEN),
- criteriaBuilder.equal(query_role.get(QueryDO_.name), queryName)
+ criteriaBuilder.equal(criteriaBuilder.lower(query_role.get(QueryDO_.name)),
+ queryName.toLowerCase())
);
criteriaQuery.select(root);
@@ -383,4 +383,4 @@
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/de/ids_mannheim/korap/init/NamedVCLoader.java b/src/main/java/de/ids_mannheim/korap/init/NamedVCLoader.java
index d1fd84b..f9e40eb 100644
--- a/src/main/java/de/ids_mannheim/korap/init/NamedVCLoader.java
+++ b/src/main/java/de/ids_mannheim/korap/init/NamedVCLoader.java
@@ -147,6 +147,7 @@
private void processVC (String vcId, String json, double apiVersion)
throws IOException, QueryException {
boolean updateCache = false;
+ vcId = vcId.toLowerCase();
try {
// if VC exists in the DB
QueryDO existingVC = vcService.searchQueryByName("system", vcId, "system",
diff --git a/src/main/java/de/ids_mannheim/korap/rewrite/VirtualCorpusRewrite.java b/src/main/java/de/ids_mannheim/korap/rewrite/VirtualCorpusRewrite.java
index 88448b8..524fe71 100644
--- a/src/main/java/de/ids_mannheim/korap/rewrite/VirtualCorpusRewrite.java
+++ b/src/main/java/de/ids_mannheim/korap/rewrite/VirtualCorpusRewrite.java
@@ -44,6 +44,7 @@
return node;
}
+ // EM: can it handle multiple vc refs?
private void findVCRef (String username, KoralNode koralNode)
throws KustvaktException {
if (koralNode.has("@type")
@@ -64,6 +65,9 @@
ownerExist = true;
}
}
+
+ String originalVcName = new String(vcName);
+ vcName = vcName.toLowerCase();
String vcInCaching = config.getVcInCaching();
if (vcName.equals(vcInCaching)) {
@@ -74,6 +78,7 @@
koralNode.get("ref"));
}
+ // ref is not lower case
QueryDO vc = queryService.searchQueryByName(username, vcName,
vcOwner, QueryType.VIRTUAL_CORPUS);
if (!vc.isCached()) {
@@ -81,7 +86,10 @@
}
// required for named-vc since they are stored by filenames in the cache
else if (ownerExist) {
- removeOwner(vc.getKoralQuery(), vcOwner, koralNode);
+ removeOwner(originalVcName, vcName, vcOwner, koralNode);
+ }
+ else if (!originalVcName.equals(vcName)) {
+ lowerVCName(originalVcName, vcName, koralNode);
}
}
@@ -98,15 +106,28 @@
}
}
- private void removeOwner (String koralQuery, String vcOwner,
- KoralNode koralNode) throws KustvaktException {
+ private void removeOwner (String originalVcName, String vcName,
+ String vcOwner, KoralNode koralNode)
+ throws KustvaktException {
JsonNode jsonNode = koralNode.rawNode();
String ref = jsonNode.at("/ref").asText();
+ ref = ref.replace(originalVcName, vcName);
String newRef = ref.substring(vcOwner.length() + 1, ref.length());
koralNode.replace("ref", newRef, new RewriteIdentifier("ref", ref,
"Ref has been replaced. The original value is described at "
+ "the original property."));
}
+
+ private void lowerVCName (String originalVcName, String vcName,
+ KoralNode koralNode)
+ throws KustvaktException {
+ JsonNode jsonNode = koralNode.rawNode();
+ String ref = jsonNode.at("/ref").asText();
+ String newRef = ref.replace(originalVcName, vcName);
+ koralNode.replace("ref", newRef, new RewriteIdentifier("ref", ref,
+ "Ref has been replaced. The original value is described at "
+ + "the original property."));
+ }
protected void rewriteVC (QueryDO vc, KoralNode koralNode)
throws KustvaktException {
@@ -133,4 +154,5 @@
// new RewriteIdentifier("ref", "", jsonNode.at("/ref").asText()));
// koralNode.setAll((ObjectNode) kq);
}
+
}
diff --git a/src/main/java/de/ids_mannheim/korap/service/QueryServiceImpl.java b/src/main/java/de/ids_mannheim/korap/service/QueryServiceImpl.java
index 095965c..1de9086 100644
--- a/src/main/java/de/ids_mannheim/korap/service/QueryServiceImpl.java
+++ b/src/main/java/de/ids_mannheim/korap/service/QueryServiceImpl.java
@@ -75,7 +75,7 @@
public static boolean DEBUG = false;
public static Pattern queryNamePattern = Pattern
- .compile("[a-zA-Z0-9]+[a-zA-Z_0-9-.]*");
+ .compile("[a-z0-9]+[a-z_0-9-.]*");
@Autowired
private QueryDao queryDao;
@@ -258,6 +258,7 @@
verifyUsername(username, queryCreator);
QueryDO query = queryDao.retrieveQueryByName(queryName, queryCreator);
+ queryName = queryName.toLowerCase();
if (query == null) {
storeQuery(queryJson, queryName, queryCreator, username,
apiVersion);
diff --git a/src/test/java/de/ids_mannheim/korap/dao/QueryCaseSensitivityTest.java b/src/test/java/de/ids_mannheim/korap/dao/QueryCaseSensitivityTest.java
new file mode 100644
index 0000000..75061cc
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/dao/QueryCaseSensitivityTest.java
@@ -0,0 +1,122 @@
+package de.ids_mannheim.korap.dao;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+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.PredefinedRole;
+import de.ids_mannheim.korap.constant.PrivilegeType;
+import de.ids_mannheim.korap.entity.QueryDO;
+import de.ids_mannheim.korap.entity.Role;
+import de.ids_mannheim.korap.entity.UserGroup;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.user.User;
+
+/** Generated by Github Copilot
+ *
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration("classpath:test-config.xml")
+public class QueryCaseSensitivityTest extends DaoTestBase {
+
+ @Autowired
+ private QueryDao queryDao;
+ @Autowired
+ private RoleDao roleDao;
+ @Autowired
+ private UserGroupDao userGroupDao;
+
+ @Test
+ public void retrieveQueryByName_caseInsensitive () throws KustvaktException {
+ String creator = "caseUser";
+ String name = "casevc";
+ int id = queryDao.createQuery(name, ResourceType.PRIVATE,
+ QueryType.VIRTUAL_CORPUS, User.CorpusAccess.FREE, "koral",
+ "def", "desc", "status", false, creator, null, null, null);
+
+ QueryDO created = queryDao.retrieveQueryById(id);
+ assertEquals(name, created.getName());
+
+ // lookup with different case for the query name
+ QueryDO found = queryDao.retrieveQueryByName("CaseVC", creator);
+ assertNotNull(found);
+ assertEquals(id, found.getId());
+
+ // cleanup
+ queryDao.deleteQuery(created);
+ }
+
+ @Test
+ public void retrieveHiddenGroupByQueryName_caseInsensitive () throws KustvaktException {
+ String groupCreator = "groupCreator";
+ // create a hidden group
+ int gid = userGroupDao.createGroup("hidden-group", null, groupCreator,
+ UserGroupStatus.HIDDEN);
+ UserGroup group = userGroupDao.retrieveGroupById(gid, true);
+
+ // create a query with mixed case name
+ String qCreator = "queryCreator";
+ String qName = "publishedvc";
+ int qid = queryDao.createQuery(qName, ResourceType.PUBLISHED,
+ QueryType.VIRTUAL_CORPUS, User.CorpusAccess.FREE, "koral",
+ "def", "desc", "status", false, qCreator, null, null, null);
+ QueryDO query = queryDao.retrieveQueryById(qid);
+
+ // attach a role linking the group and the query
+ Role r = new Role(PredefinedRole.GROUP_ADMIN, PrivilegeType.READ_MEMBER,
+ group, query);
+ roleDao.addRole(r);
+
+ // lookup hidden group by query name using different case
+ UserGroup found = userGroupDao.retrieveHiddenGroupByQueryName("PublishedVC");
+ assertNotNull(found);
+ assertEquals(gid, found.getId());
+
+ // cleanup
+ deleteUserGroup(gid, groupCreator);
+ queryDao.deleteQuery(query);
+ }
+
+ @Test
+ public void deleteRoleByGroupAndQuery_queryName_caseInsensitive () throws KustvaktException {
+ // create a fresh group using helper
+ UserGroup group = createUserGroup("del-group", "deleter");
+
+ String qCreator = "queryCreator";
+ String qName = "delvc";
+ int qid = queryDao.createQuery(qName, ResourceType.PRIVATE,
+ QueryType.VIRTUAL_CORPUS, User.CorpusAccess.FREE, "koral",
+ "def", "desc", "status", false, qCreator, null, null, null);
+ QueryDO query = queryDao.retrieveQueryById(qid);
+
+ // add a role linking group and query
+ Role r = new Role(PredefinedRole.GROUP_ADMIN, PrivilegeType.READ_MEMBER,
+ group, query);
+ roleDao.addRole(r);
+
+ // ensure role exists for the query
+ List<Role> rolesBefore = roleDao.retrieveRolesByQueryIdWithMembers(qid);
+ assertTrue(rolesBefore.size() >= 1);
+
+ // delete by group name and different-case query name
+ roleDao.deleteRoleByGroupAndQuery(group.getName(), qCreator, "DelVC");
+
+ List<Role> rolesAfter = roleDao.retrieveRolesByQueryIdWithMembers(qid);
+ assertEquals(0, rolesAfter.size());
+
+ // cleanup
+ deleteUserGroup(group.getId(), "deleter");
+ queryDao.deleteQuery(query);
+ }
+}
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
index dd0e4b8..b85dcde 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
@@ -71,6 +71,7 @@
assertEquals("new_vc", node.get(1).get("name").asText());
testCreateVC_sameName(testUser, "new_vc", ResourceType.PRIVATE);
+ testCreateVC_sameName(testUser, "NEW_VC", ResourceType.PRIVATE);
// delete new VC
deleteVC("new_vc", testUser, testUser);
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusReferenceTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusReferenceTest.java
index da1cb9e..2580a0a 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusReferenceTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusReferenceTest.java
@@ -51,12 +51,15 @@
assertTrue(VirtualCorpusCache.contains("named-vc1"));
JsonNode node = testSearchWithRef_VC1();
assertEquals(numOfMatches, node.at("/matches").size());
+ testSearchIncaseSensitivity_VC1(numOfMatches);
+
testStatisticsWithRef();
numOfMatches = testSearchWithoutRef_VC2();
vcLoader.loadVCToCache("named-vc2", "/vc/named-vc2.jsonld");
assertTrue(VirtualCorpusCache.contains("named-vc2"));
node = testSearchWithRef_VC2();
assertEquals(numOfMatches, node.at("/matches").size());
+ testSearchIncaseSensitivity_VC2(numOfMatches);
testDeleteVC("named-vc1", "system", admin);
testDeleteVC("named-vc2", "system", admin);
@@ -96,6 +99,16 @@
String ent = response.readEntity(String.class);
return JsonUtils.readTree(ent);
}
+
+ private void testSearchIncaseSensitivity_VC1 (int numOfMatches) throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"system/Named-VC1\"").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(numOfMatches, node.at("/matches").size());
+ }
private JsonNode testSearchWithRef_VC2 () throws KustvaktException {
Response response = target().path(API_VERSION).path("search")
@@ -104,6 +117,17 @@
String ent = response.readEntity(String.class);
return JsonUtils.readTree(ent);
}
+
+ private void testSearchIncaseSensitivity_VC2 (int numOfMatches)
+ throws KustvaktException {
+ Response response = target().path(API_VERSION).path("search")
+ .queryParam("q", "[orth=der]").queryParam("ql", "poliqarp")
+ .queryParam("cq", "referTo \"Named-VC2\"").request()
+ .get();
+ String ent = response.readEntity(String.class);
+ JsonNode node = JsonUtils.readTree(ent);
+ assertEquals(numOfMatches, node.at("/matches").size());
+ }
private void testStatisticsWithRef () throws KustvaktException {
String corpusQuery = "availability = /CC.*/ & referTo named-vc1";