This is an automated email from the ASF dual-hosted git repository.

andreapatricelli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 0f250a2316 [SYNCOPE-1779] supporting underscore in search queries 
(#529)
0f250a2316 is described below

commit 0f250a2316c64c9f441574b50ef605ebbcb9d12f
Author: Andrea Patricelli <andreapatrice...@apache.org>
AuthorDate: Wed Oct 11 14:39:20 2023 +0200

    [SYNCOPE-1779] supporting underscore in search queries (#529)
    
    * [SYNCOPE-1779] supporting underscore in search queries, with specific 
changes for Oracle DB
---
 .../persistence/api/search/SearchCondVisitor.java  |  3 +-
 .../api/search/SearchCondConverterTest.java        | 13 ++++++++
 .../persistence/jpa/dao/OJPAJSONAnySearchDAO.java  |  5 +++
 .../core/persistence/jpa/dao/AbstractDAO.java      | 22 ++++++++++++
 .../core/persistence/jpa/dao/JPAAnySearchDAO.java  |  5 +++
 .../org/apache/syncope/fit/core/SearchITCase.java  | 39 ++++++++++++++++++++++
 6 files changed, 85 insertions(+), 2 deletions(-)

diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
index 92de4a349e..6496411476 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
@@ -76,8 +76,7 @@ public class SearchCondVisitor extends 
AbstractSearchConditionVisitor<SearchBean
 
     protected static String getValue(final SearchCondition<SearchBean> sc) {
         String value = SearchUtils.toSqlWildcardString(
-                URLDecoder.decode(sc.getStatement().getValue().toString(), 
StandardCharsets.UTF_8), false).
-                replaceAll("\\\\_", "_");
+                URLDecoder.decode(sc.getStatement().getValue().toString(), 
StandardCharsets.UTF_8), false);
 
         // see SYNCOPE-1321
         if (TIMEZONE.matcher(value).matches()) {
diff --git 
a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
 
b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
index eff4efd305..a98dc321c9 100644
--- 
a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
+++ 
b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
@@ -329,4 +329,17 @@ public class SearchCondConverterTest {
 
         assertEquals(SearchCond.getLeaf(cond), 
SearchCondConverter.convert(VISITOR, fiql));
     }
+
+    @Test
+    public void issueSYNCOPE1779() {
+        String fiql = new 
UserFiqlSearchConditionBuilder().is("username").equalToIgnoreCase("ros_*").query();
+        assertEquals("username=~ros_*", fiql);
+
+        AttrCond attrCond = new AnyCond(AttrCond.Type.ILIKE);
+        attrCond.setSchema("username");
+        attrCond.setExpression("ros\\_%");
+        SearchCond leaf = SearchCond.getLeaf(attrCond);
+
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
+    }
 }
diff --git 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java
 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java
index ad4aaa9434..67216ee358 100644
--- 
a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java
+++ 
b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OJPAJSONAnySearchDAO.java
@@ -244,6 +244,11 @@ public class OJPAJSONAnySearchDAO extends JPAAnySearchDAO {
             query.append(lower ? "LOWER(" : "").
                     append('?').append(setParameter(parameters, value)).
                     append(lower ? ")" : "");
+            // workaround for Oracle DB adding explicit escaping string, to 
search 
+            // for literal _ (underscore) (SYNCOPE-1779)
+            if (cond.getType() == AttrCond.Type.ILIKE || cond.getType() == 
AttrCond.Type.LIKE) {
+                query.append(" ESCAPE '\\' ");
+            }
         }
     }
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java
index 6f3863ccdb..8a529246e4 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java
@@ -20,7 +20,14 @@ package org.apache.syncope.core.persistence.jpa.dao;
 
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.EntityManagerFactory;
+import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.openjpa.jdbc.meta.MappingRepository;
+import org.apache.openjpa.jdbc.sql.OracleDictionary;
+import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
+import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
+import org.apache.openjpa.persistence.OpenJPAPersistence;
 import org.apache.syncope.core.persistence.api.dao.DAO;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
@@ -33,6 +40,8 @@ public abstract class AbstractDAO<E extends Entity> 
implements DAO<E> {
 
     protected static final Logger LOG = LoggerFactory.getLogger(DAO.class);
 
+    private static final Map<String, Boolean> IS_ORACLE = new 
ConcurrentHashMap<>();
+    
     protected EntityManagerFactory entityManagerFactory() {
         return EntityManagerFactoryUtils.findEntityManagerFactory(
                 ApplicationContextProvider.getBeanFactory(), 
AuthContextUtils.getDomain());
@@ -53,4 +62,17 @@ public abstract class AbstractDAO<E extends Entity> 
implements DAO<E> {
     public void detach(final E entity) {
         entityManager().detach(entity);
     }
+
+    protected boolean isOracle() {
+        Boolean isOracle = IS_ORACLE.get(AuthContextUtils.getDomain());
+        if (isOracle == null) {
+            OpenJPAEntityManagerFactory emf = 
OpenJPAPersistence.cast(entityManagerFactory());
+            OpenJPAEntityManagerFactorySPI emfspi = 
(OpenJPAEntityManagerFactorySPI) OpenJPAPersistence.cast(emf);
+            isOracle = ((MappingRepository) emfspi.getConfiguration()
+                    .getMetaDataRepositoryInstance()).getDBDictionary() 
instanceof OracleDictionary;
+            IS_ORACLE.put(AuthContextUtils.getDomain(), isOracle);
+
+        }
+        return isOracle;
+    }
 }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index 622f8dd505..4e6d29aeb2 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -999,6 +999,11 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
                         } else {
                             query.append('?').append(setParameter(parameters, 
cond.getExpression()));
                         }
+                        // workaround for Oracle DB adding explicit escaping 
string, to search 
+                        // for literal _ (underscore) (SYNCOPE-1779)
+                        if (isOracle()) {
+                            query.append(" ESCAPE '\\' ");
+                        }
                     } else {
                         if (!(cond instanceof AnyCond)) {
                             query.append("' AND");
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
index fe0eb5663d..bb13fe418f 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
@@ -875,4 +875,43 @@ public class SearchITCase extends AbstractITCase {
         assertEquals(1, users.getResult().size());
         assertEquals(user.getKey(), users.getResult().get(0).getKey());
     }
+
+    @Test
+    public void issueSYNCOPE1779() {
+        // 1. create user with underscore
+        UserTO userWithUnderscore = 
createUser(UserITCase.getSample("syncope1779_t...@syncope.apache.org")).getEntity();
+        // 2 create second user without underscore
+        
createUser(UserITCase.getSample("syncope1779t...@syncope.apache.org")).getEntity();
+
+        // 3. search for user
+        if (IS_ELASTICSEARCH_ENABLED) {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+
+        // Search by username
+        PagedResult<UserTO> users = USER_SERVICE.search(new 
AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM)
+                
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("syncope1779_*")
+                        .and().is("firstname").equalTo("syncope1779_*")
+                        .and().is("userId").equalTo("syncope1779_*").query())
+                .build());
+        assertEquals(1, users.getResult().size());
+        assertEquals(userWithUnderscore.getKey(), 
users.getResult().get(0).getKey());
+        // Search also by attribute
+        users = USER_SERVICE.search(new 
AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM)
+                
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("email").equalTo("syncope1779_*").query())
+                .build());
+        assertEquals(1, users.getResult().size());
+        assertEquals(userWithUnderscore.getKey(), 
users.getResult().get(0).getKey());
+        // search for both
+        users = USER_SERVICE.search(new 
AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM)
+                
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("email").equalTo("syncope1779*").query())
+                .build());
+        assertEquals(2, users.getResult().size());
+
+        users.getResult().forEach(u -> USER_SERVICE.delete(u.getKey()));
+    }
 }

Reply via email to