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

shuber pushed a commit to branch opensearch-persistence
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit 16c6de9114fee82110534ea13239cc59daadbee5
Author: Serge Huber <shu...@jahia.com>
AuthorDate: Sun Dec 22 10:57:00 2024 +0100

    Work on making integration tests work with OpenSearch:
    - Removed elasticsearch-core from bundle watch requirements
    - Fix issues with date parsing due to case sensitivity
    - Improved test units for date parsing and date math handling
    - Modified HealthChecks to provide an OpenSearch check provider (not yet 
fully working)
    - Deactivate 1.x to 2.x migration integration test for OpenSearch (No 
OpenSearch users will be coming from 1.x)
    - Update OpenSearch past event query builder to latest changes done in 
ElasticSearch past event query builder
    - Various fixes in the integration tests to make them compatible with 
OpenSearch (removed hardcoded elasticsearch configuration and references)
    - Added new shell script in itests directory to make it easier to handle 
the dynamically generated Pax Exam Karaf test container directory. 
Documentation is also included in the README file inside the itests directory.
---
 .../unomi/healthcheck/HealthCheckConfig.java       |  7 ++-
 .../provider/ElasticSearchHealthCheckProvider.java |  5 +-
 ...der.java => OpenSearchHealthCheckProvider.java} | 31 +++++-----
 .../resources/org.apache.unomi.healthcheck.cfg     | 11 +++-
 itests/README.md                                   | 51 +++++++++++++++++
 .../test/java/org/apache/unomi/itests/AllITs.java  |  4 +-
 .../test/java/org/apache/unomi/itests/BaseIT.java  |  7 +++
 ...BuilderIT.java => ConditionQueryBuilderIT.java} |  4 +-
 .../org/apache/unomi/itests/HealthCheckIT.java     | 54 +-----------------
 .../org/apache/unomi/itests/ProfileServiceIT.java  | 14 ++---
 .../itests/ProfileServiceWithoutOverwriteIT.java   |  8 ++-
 .../unomi/itests/migration/Migrate16xTo220IT.java  | 17 ++++++
 .../resources/org.apache.unomi.healthcheck.cfg     |  9 ++-
 .../resources/OSGI-INF/blueprint/blueprint.xml     |  1 -
 .../PastEventConditionOSQueryBuilder.java          | 66 ++++++++++------------
 .../resources/META-INF/cxs/mappings/profile.json   |  7 +++
 .../persistence/spi/conditions/DateUtils.java      |  2 +-
 .../spi/conditions/datemath/DateMathParser.java    | 17 +++++-
 .../spi/conditions/datemath/JavaDateFormatter.java | 12 ++++
 .../conditions/datemath/DateMathParserTest.java    | 50 ++++++++++++++++
 .../conditions/datemath/JavaDateFormatterTest.java | 43 +++++++++++++-
 21 files changed, 290 insertions(+), 130 deletions(-)

diff --git 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckConfig.java
 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckConfig.java
index e86018dc3..9cd7a2662 100644
--- 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckConfig.java
+++ 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckConfig.java
@@ -40,7 +40,12 @@ public class HealthCheckConfig {
     public static final String CONFIG_ES_SSL_ENABLED = "esSSLEnabled";
     public static final String CONFIG_ES_LOGIN = "esLogin";
     public static final String CONFIG_ES_PASSWORD = "esPassword";
-    public static final String CONFIG_TRUST_ALL_CERTIFICATES = 
"httpClient.trustAllCertificates";
+    public static final String CONFIG_ES_TRUST_ALL_CERTIFICATES = 
"esHttpClient.trustAllCertificates";
+    public static final String CONFIG_OS_ADDRESSES = "osAddresses";
+    public static final String CONFIG_OS_SSL_ENABLED = "osSSLEnabled";
+    public static final String CONFIG_OS_LOGIN = "osLogin";
+    public static final String CONFIG_OS_PASSWORD = "osPassword";
+    public static final String CONFIG_OS_TRUST_ALL_CERTIFICATES = 
"osHttpClient.trustAllCertificates";
     public static final String CONFIG_AUTH_REALM = "authentication.realm";
     public static final String ENABLED = "healthcheck.enabled";
     public static final String PROVIDERS = "healthcheck.providers";
diff --git 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/ElasticSearchHealthCheckProvider.java
 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/ElasticSearchHealthCheckProvider.java
index 361e68df7..1dc9e146e 100644
--- 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/ElasticSearchHealthCheckProvider.java
+++ 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/ElasticSearchHealthCheckProvider.java
@@ -26,11 +26,10 @@ import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
 import org.apache.unomi.healthcheck.HealthCheckConfig;
-import org.apache.unomi.healthcheck.HealthCheckResponse;
 import org.apache.unomi.healthcheck.HealthCheckProvider;
+import org.apache.unomi.healthcheck.HealthCheckResponse;
 import org.apache.unomi.healthcheck.util.CachedValue;
 import org.apache.unomi.shell.migration.utils.HttpUtils;
 import org.osgi.service.component.annotations.Activate;
@@ -77,7 +76,7 @@ public class ElasticSearchHealthCheckProvider implements 
HealthCheckProvider {
         }
         try {
             httpClient = HttpUtils.initHttpClient(
-                    
Boolean.parseBoolean(config.get(HealthCheckConfig.CONFIG_TRUST_ALL_CERTIFICATES)),
 credentialsProvider);
+                    
Boolean.parseBoolean(config.get(HealthCheckConfig.CONFIG_ES_TRUST_ALL_CERTIFICATES)),
 credentialsProvider);
         } catch (IOException e) {
             LOGGER.error("Unable to initialize http client", e);
         }
diff --git 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/ElasticSearchHealthCheckProvider.java
 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/OpenSearchHealthCheckProvider.java
similarity index 82%
copy from 
extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/ElasticSearchHealthCheckProvider.java
copy to 
extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/OpenSearchHealthCheckProvider.java
index 361e68df7..3524d1daa 100644
--- 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/ElasticSearchHealthCheckProvider.java
+++ 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/provider/OpenSearchHealthCheckProvider.java
@@ -26,11 +26,10 @@ import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
 import org.apache.unomi.healthcheck.HealthCheckConfig;
-import org.apache.unomi.healthcheck.HealthCheckResponse;
 import org.apache.unomi.healthcheck.HealthCheckProvider;
+import org.apache.unomi.healthcheck.HealthCheckResponse;
 import org.apache.unomi.healthcheck.util.CachedValue;
 import org.apache.unomi.shell.migration.utils.HttpUtils;
 import org.osgi.service.component.annotations.Activate;
@@ -44,15 +43,15 @@ import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
 /**
- * A Health Check that checks the status of the ElasticSearch connectivity 
according to the provided configuration.
+ * A Health Check that checks the status of the OpenSearch connectivity 
according to the provided configuration.
  * This connectivity should be LIVE before any try to start Unomi.
  */
 @Component(service = HealthCheckProvider.class, immediate = true)
-public class ElasticSearchHealthCheckProvider implements HealthCheckProvider {
+public class OpenSearchHealthCheckProvider implements HealthCheckProvider {
 
-    public static final String NAME = "elasticsearch";
+    public static final String NAME = "opensearch";
 
-    private static final Logger LOGGER = 
LoggerFactory.getLogger(ElasticSearchHealthCheckProvider.class.getName());
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(OpenSearchHealthCheckProvider.class.getName());
     private final CachedValue<HealthCheckResponse> cache = new 
CachedValue<>(10, TimeUnit.SECONDS);
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
@@ -60,24 +59,24 @@ public class ElasticSearchHealthCheckProvider implements 
HealthCheckProvider {
 
     private CloseableHttpClient httpClient;
 
-    public ElasticSearchHealthCheckProvider() {
-        LOGGER.info("Building elasticsearch health provider service...");
+    public OpenSearchHealthCheckProvider() {
+        LOGGER.info("Building OpenSearch health provider service...");
     }
 
     @Activate
     public void activate() {
-        LOGGER.info("Activating elasticsearch health provider service...");
+        LOGGER.info("Activating OpenSearch health provider service...");
         CredentialsProvider credentialsProvider = null;
-        String login = config.get(HealthCheckConfig.CONFIG_ES_LOGIN);
+        String login = config.get(HealthCheckConfig.CONFIG_OS_LOGIN); // Reuse 
ElasticSearch credentials key
         if (StringUtils.isNotEmpty(login)) {
             credentialsProvider = new BasicCredentialsProvider();
             UsernamePasswordCredentials credentials
-                    = new UsernamePasswordCredentials(login, 
config.get(HealthCheckConfig.CONFIG_ES_PASSWORD));
+                    = new UsernamePasswordCredentials(login, 
config.get(HealthCheckConfig.CONFIG_OS_PASSWORD));
             credentialsProvider.setCredentials(AuthScope.ANY, credentials);
         }
         try {
             httpClient = HttpUtils.initHttpClient(
-                    
Boolean.parseBoolean(config.get(HealthCheckConfig.CONFIG_TRUST_ALL_CERTIFICATES)),
 credentialsProvider);
+                    
Boolean.parseBoolean(config.get(HealthCheckConfig.CONFIG_OS_TRUST_ALL_CERTIFICATES)),
 credentialsProvider);
         } catch (IOException e) {
             LOGGER.error("Unable to initialize http client", e);
         }
@@ -92,7 +91,7 @@ public class ElasticSearchHealthCheckProvider implements 
HealthCheckProvider {
     }
 
     @Override public HealthCheckResponse execute() {
-        LOGGER.debug("Health check elasticsearch");
+        LOGGER.debug("Health check OpenSearch");
         if (cache.isStaled() || cache.getValue().isDown() || 
cache.getValue().isError()) {
             cache.setValue(refresh());
         }
@@ -104,8 +103,8 @@ public class ElasticSearchHealthCheckProvider implements 
HealthCheckProvider {
         HealthCheckResponse.Builder builder = new 
HealthCheckResponse.Builder();
         builder.name(NAME).down();
         String url = 
(config.get(HealthCheckConfig.CONFIG_ES_SSL_ENABLED).equals("true") ? 
"https://"; : "http://";)
-                        
.concat(config.get(HealthCheckConfig.CONFIG_ES_ADDRESSES).split(",")[0].trim())
-                        .concat("/_cluster/health");
+                
.concat(config.get(HealthCheckConfig.CONFIG_ES_ADDRESSES).split(",")[0].trim())
+                .concat("/_cluster/health");
         CloseableHttpResponse response = null;
         try {
             response = httpClient.execute(new HttpGet(url));
@@ -119,7 +118,7 @@ public class ElasticSearchHealthCheckProvider implements 
HealthCheckProvider {
             }
         } catch (IOException e) {
             builder.error().withData("error", e.getMessage());
-            LOGGER.error("Error while checking elasticsearch health", e);
+            LOGGER.error("Error while checking OpenSearch health", e);
         } finally {
             if (response != null) {
                 EntityUtils.consumeQuietly(response.getEntity());
diff --git 
a/extensions/healthcheck/src/main/resources/org.apache.unomi.healthcheck.cfg 
b/extensions/healthcheck/src/main/resources/org.apache.unomi.healthcheck.cfg
index 4cbe767eb..b73f7ca4d 100644
--- a/extensions/healthcheck/src/main/resources/org.apache.unomi.healthcheck.cfg
+++ b/extensions/healthcheck/src/main/resources/org.apache.unomi.healthcheck.cfg
@@ -20,12 +20,19 @@ esAddresses = 
${org.apache.unomi.elasticsearch.addresses:-localhost:9200}
 esSSLEnabled = ${org.apache.unomi.elasticsearch.sslEnable:-false}
 esLogin = ${org.apache.unomi.elasticsearch.username:-}
 esPassword = ${org.apache.unomi.elasticsearch.password:-}
-httpClient.trustAllCertificates = 
${org.apache.unomi.elasticsearch.sslTrustAllCertificates:-false}
+esHttpClient.trustAllCertificates = 
${org.apache.unomi.elasticsearch.sslTrustAllCertificates:-false}
+
+# OpenSearch configuration
+osAddresses = ${org.apache.unomi.opensearch.addresses:-localhost:9200}
+osSSLEnabled = ${org.apache.unomi.opensearch.sslEnable:-true}
+osLogin = ${org.apache.unomi.opensearch.username:-admin}
+osPassword = ${org.apache.unomi.opensearch.password:-}
+osHttpClient.trustAllCertificates = 
${org.apache.unomi.opensearch.sslTrustAllCertificates:-true}
 
 # Security configuration
 authentication.realm = ${org.apache.unomi.security.realm:-karaf}
 
 # Health check configuration
 healthcheck.enabled = ${org.apache.unomi.healthcheck.enabled:-false}
-healthcheck.providers = 
${org.apache.unomi.healthcheck.providers:-cluster,elasticsearch,unomi,persistence}
+healthcheck.providers = 
${org.apache.unomi.healthcheck.providers:-cluster,elasticsearch,opensearch,unomi,persistence}
 healthcheck.timeout = ${org.apache.unomi.healthcheck.timeout:-400}
diff --git a/itests/README.md b/itests/README.md
index 85e5aca2d..c24eee02a 100644
--- a/itests/README.md
+++ b/itests/README.md
@@ -274,3 +274,54 @@ And the final step is, zipping the new version of the 
snapshot repository and re
 > In case you are using docker, do zip in the container and use `docker cp` to 
 > get the zip file from the docker container.
 
 Now you can modify the migration test class to test that your added data in 
1.6.x is correctly migrated in 2.0.0
+
+# Integration Tests
+
+This directory contains the integration tests for Apache Unomi.
+
+## Karaf Tools
+
+The `kt.sh` script (short for "Karaf Tools") provides convenient utilities for 
working with Karaf logs and directories during integration testing. Since Karaf 
test directories are created with unique UUIDs for each test run, this script 
helps locate and work with the latest test instance.
+
+### Usage
+
+```bash
+./kt.sh COMMAND [ARGS]
+```
+
+### Available Commands
+
+| Command      | Alias | Description                                           
|
+|-------------|-------|-------------------------------------------------------|
+| `log`       | `l`   | View the latest Karaf log file using less            |
+| `tail`      | `t`   | Tail the current Karaf log file                      |
+| `grep`      | `g`   | Grep the latest Karaf log file (requires pattern)    |
+| `dir`       | `d`   | Print the latest Karaf directory path                |
+| `pushd`     | `p`   | Change to the latest Karaf directory using pushd     |
+| `help`      | `h`   | Show help message                                    |
+
+### Examples
+
+```bash
+# View log with less
+./kt.sh log
+
+# Tail log file
+./kt.sh tail
+
+# Search for ERROR in log file
+./kt.sh grep ERROR
+
+# Print Karaf directory path
+./kt.sh dir
+
+# Change to Karaf directory
+./kt.sh pushd
+```
+
+### Tips
+
+- The script automatically finds the most recently created Karaf test directory
+- All commands have short aliases (single letter) for faster typing
+- Error handling is included for missing directories and files
+- The script is particularly useful when debugging integration test failures
diff --git a/itests/src/test/java/org/apache/unomi/itests/AllITs.java 
b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
index f415c3ab9..bc6614a17 100644
--- a/itests/src/test/java/org/apache/unomi/itests/AllITs.java
+++ b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
@@ -17,8 +17,8 @@
 
 package org.apache.unomi.itests;
 
-import org.apache.unomi.itests.migration.Migrate16xTo220IT;
 import org.apache.unomi.itests.graphql.*;
+import org.apache.unomi.itests.migration.Migrate16xTo220IT;
 import org.apache.unomi.itests.migration.MigrationIT;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -35,7 +35,7 @@ import org.junit.runners.Suite.SuiteClasses;
         MigrationIT.class,
         BasicIT.class,
         ConditionEvaluatorIT.class,
-        ConditionESQueryBuilderIT.class,
+        ConditionQueryBuilderIT.class,
         SegmentIT.class,
         ProfileServiceIT.class,
         ProfileImportBasicIT.class,
diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java 
b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index 8e2bece4a..b598195ce 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -293,10 +293,17 @@ public abstract class BaseIT extends KarafTestSupport {
                 editConfigurationFilePut("etc/org.ops4j.pax.logging.cfg", 
"log4j2.rootLogger.level", "INFO"),
                 editConfigurationFilePut("etc/org.apache.karaf.features.cfg", 
"serviceRequirements", "disable"),
                 editConfigurationFilePut("etc/system.properties", 
"my.system.property", System.getProperty("my.system.property")),
+                editConfigurationFilePut("etc/system.properties", 
SEARCH_ENGINE_PROPERTY, System.getProperty(SEARCH_ENGINE_PROPERTY, 
SEARCH_ENGINE_ELASTICSEARCH)),
                 editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.graphql.feature.activated", "true"),
                 editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.elasticsearch.cluster.name", "contextElasticSearchITests"),
                 editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.elasticsearch.addresses", "localhost:9400"),
                 editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.elasticsearch.taskWaitingPollingInterval", "50"),
+                editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.opensearch.cluster.name", "contextElasticSearchITests"),
+                editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.opensearch.addresses", "localhost:9400"),
+                editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.opensearch.username", "admin"),
+                editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.opensearch.password", "Unomi.1ntegrat10n.Tests"),
+                editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.opensearch.sslEnable", "false"),
+                editConfigurationFilePut("etc/custom.system.properties", 
"org.apache.unomi.opensearch.sslTrustAllCertificates", "true"),
 
                 
systemProperty("org.ops4j.pax.exam.rbc.rmi.port").value("1199"),
                 
systemProperty("org.apache.unomi.hazelcast.group.name").value("cellar"),
diff --git 
a/itests/src/test/java/org/apache/unomi/itests/ConditionESQueryBuilderIT.java 
b/itests/src/test/java/org/apache/unomi/itests/ConditionQueryBuilderIT.java
similarity index 95%
rename from 
itests/src/test/java/org/apache/unomi/itests/ConditionESQueryBuilderIT.java
rename to 
itests/src/test/java/org/apache/unomi/itests/ConditionQueryBuilderIT.java
index 06e57f484..882364db4 100644
--- 
a/itests/src/test/java/org/apache/unomi/itests/ConditionESQueryBuilderIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ConditionQueryBuilderIT.java
@@ -30,13 +30,13 @@ import org.ops4j.pax.exam.spi.reactors.PerSuite;
 import java.util.List;
 
 /**
- * Integration tests for various condition query builder types (elasticsearch).
+ * Integration tests for various condition query builder types (ElasticSearch 
or OpenSearch).
  *
  * @author Sergiy Shyrkov
  */
 @RunWith(PaxExam.class)
 @ExamReactorStrategy(PerSuite.class)
-public class ConditionESQueryBuilderIT extends ConditionEvaluatorIT {
+public class ConditionQueryBuilderIT extends ConditionEvaluatorIT {
 
     @Override
     protected boolean eval(Condition c) {
diff --git a/itests/src/test/java/org/apache/unomi/itests/HealthCheckIT.java 
b/itests/src/test/java/org/apache/unomi/itests/HealthCheckIT.java
index 2ef00efa8..48d48e578 100644
--- a/itests/src/test/java/org/apache/unomi/itests/HealthCheckIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/HealthCheckIT.java
@@ -17,78 +17,26 @@
 
 package org.apache.unomi.itests;
 
-import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
-import org.apache.commons.io.IOUtils;
-import org.apache.cxf.interceptor.security.AccessDeniedException;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.config.Registry;
-import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.socket.ConnectionSocketFactory;
-import org.apache.http.conn.socket.PlainConnectionSocketFactory;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.entity.ContentType;
 import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.apache.karaf.itests.KarafTestSupport;
-import org.apache.unomi.api.services.DefinitionsService;
-import org.apache.unomi.api.services.EventService;
-import org.apache.unomi.api.services.ProfileService;
-import org.apache.unomi.lifecycle.BundleWatcher;
-import org.apache.unomi.persistence.spi.PersistenceService;
-import org.junit.After;
 import org.junit.Assert;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.ops4j.pax.exam.Configuration;
-import org.ops4j.pax.exam.CoreOptions;
-import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.junit.PaxExam;
-import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;
-import org.ops4j.pax.exam.options.MavenArtifactUrlReference;
-import org.ops4j.pax.exam.options.extra.VMOption;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerSuite;
-import org.ops4j.pax.exam.util.Filter;
-import org.osgi.service.cm.ConfigurationAdmin;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.inject.Inject;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Stream;
 
 import static org.junit.Assert.fail;
-import static org.ops4j.pax.exam.CoreOptions.systemProperty;
-import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.*;
 
 /**
  * Health Check Integration Tests
@@ -110,7 +58,7 @@ public class HealthCheckIT extends BaseIT {
             LOGGER.info("health check response: {}", response);
             Assert.assertEquals(5, response.size());
             Assert.assertTrue(response.stream().anyMatch(r -> 
r.getName().equals("karaf") && r.getStatus() == 
HealthCheckResponse.Status.LIVE));
-            Assert.assertTrue(response.stream().anyMatch(r -> 
r.getName().equals("elasticsearch") && r.getStatus() == 
HealthCheckResponse.Status.LIVE));
+            Assert.assertTrue(response.stream().anyMatch(r -> 
r.getName().equals(searchEngine) && r.getStatus() == 
HealthCheckResponse.Status.LIVE));
             Assert.assertTrue(response.stream().anyMatch(r -> 
r.getName().equals("unomi") && r.getStatus() == 
HealthCheckResponse.Status.LIVE));
             Assert.assertTrue(response.stream().anyMatch(r -> 
r.getName().equals("cluster") && r.getStatus() == 
HealthCheckResponse.Status.LIVE));
             Assert.assertTrue(response.stream().anyMatch(r -> 
r.getName().equals("persistence") && r.getStatus() == 
HealthCheckResponse.Status.LIVE));
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java 
b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
index 623904938..3313a753d 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceIT.java
@@ -154,21 +154,21 @@ public class ProfileServiceIT extends BaseIT {
     public void testGetProfileWithWrongScrollerIdThrowException()
             throws InterruptedException, NoSuchFieldException, 
IllegalAccessException, IOException {
         boolean throwExceptionCurrent = false;
-        Configuration elasticSearchConfiguration = 
configurationAdmin.getConfiguration("org.apache.unomi.persistence.elasticsearch");
-        if (elasticSearchConfiguration != null && 
elasticSearchConfiguration.getProperties().get("throwExceptions") != null) {
+        Configuration searchEngineConfiguration = 
configurationAdmin.getConfiguration("org.apache.unomi.persistence." + 
searchEngine);
+        if (searchEngineConfiguration != null && 
searchEngineConfiguration.getProperties().get("throwExceptions") != null) {
             try {
-                if 
(elasticSearchConfiguration.getProperties().get("throwExceptions") instanceof 
String) {
-                    throwExceptionCurrent = Boolean.parseBoolean((String) 
elasticSearchConfiguration.getProperties().get("throwExceptions"));
+                if 
(searchEngineConfiguration.getProperties().get("throwExceptions") instanceof 
String) {
+                    throwExceptionCurrent = Boolean.parseBoolean((String) 
searchEngineConfiguration.getProperties().get("throwExceptions"));
                 } else {
                     // already a boolean
-                    throwExceptionCurrent = (Boolean) 
elasticSearchConfiguration.getProperties().get("throwExceptions");
+                    throwExceptionCurrent = (Boolean) 
searchEngineConfiguration.getProperties().get("throwExceptions");
                 }
             } catch (Throwable e) {
                 // Not able to cast the property
             }
         }
 
-        updateConfiguration(PersistenceService.class.getName(), 
"org.apache.unomi.persistence.elasticsearch", "throwExceptions", true);
+        updateConfiguration(PersistenceService.class.getName(), 
"org.apache.unomi.persistence." + searchEngine, "throwExceptions", true);
 
         Query query = new Query();
         query.setLimit(2);
@@ -181,7 +181,7 @@ public class ProfileServiceIT extends BaseIT {
         } catch (RuntimeException ex) {
             // Should get here since this scenario should throw exception
         } finally {
-            updateConfiguration(PersistenceService.class.getName(), 
"org.apache.unomi.persistence.elasticsearch", "throwExceptions",
+            updateConfiguration(PersistenceService.class.getName(), 
"org.apache.unomi.persistence." + searchEngine, "throwExceptions",
                     throwExceptionCurrent);
         }
     }
diff --git 
a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceWithoutOverwriteIT.java
 
b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceWithoutOverwriteIT.java
index c6d4daeac..6f2b375cb 100644
--- 
a/itests/src/test/java/org/apache/unomi/itests/ProfileServiceWithoutOverwriteIT.java
+++ 
b/itests/src/test/java/org/apache/unomi/itests/ProfileServiceWithoutOverwriteIT.java
@@ -44,10 +44,14 @@ public class ProfileServiceWithoutOverwriteIT extends 
BaseIT {
 
     @Configuration
     public Option[] config() {
+
+        searchEngine = System.getProperty(SEARCH_ENGINE_PROPERTY, 
SEARCH_ENGINE_ELASTICSEARCH);
+        System.out.println("Search Engine: " + searchEngine);
+
         List<Option> options = new ArrayList<>();
         options.addAll(Arrays.asList(super.config()));
-        
options.add(systemProperty("org.apache.unomi.elasticsearch.throwExceptions").value("true"));
-        
options.add(systemProperty("org.apache.unomi.elasticsearch.alwaysOverwrite").value("false"));
+        
options.add(systemProperty("org.apache.unomi."+searchEngine+".throwExceptions").value("true"));
+        
options.add(systemProperty("org.apache.unomi."+searchEngine+".alwaysOverwrite").value("false"));
         return options.toArray(new Option[0]);
     }
 
diff --git 
a/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xTo220IT.java 
b/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xTo220IT.java
index 316b0bc28..f83717ee1 100644
--- 
a/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xTo220IT.java
+++ 
b/itests/src/test/java/org/apache/unomi/itests/migration/Migrate16xTo220IT.java
@@ -48,9 +48,21 @@ public class Migrate16xTo220IT extends BaseIT {
             "context-userlist", "context-propertytype", "context-scope", 
"context-conditiontype", "context-rule", "context-scoring", "context-segment", 
"context-groovyaction", "context-topic",
             "context-patch", "context-jsonschema", "context-importconfig", 
"context-exportconfig", "context-rulestats");
 
+    public void checkSearchEngine() {
+        searchEngine = System.getProperty(SEARCH_ENGINE_PROPERTY, 
SEARCH_ENGINE_ELASTICSEARCH);
+        System.out.println("Check search engine: " + searchEngine);
+    }
+
     @Override
     @Before
     public void waitForStartup() throws InterruptedException {
+        checkSearchEngine();
+
+        if (SEARCH_ENGINE_OPENSEARCH.equals(searchEngine)) {
+            System.out.println("Migration from 1.x to 2.x not supported for 
OpenSearch, skipping snapshot restore");
+            super.waitForStartup();
+            return;
+        }
 
         System.out.println("Restoring snapshot into search engine...");
         LOGGER.info("Restoring snapshot into search engine...");
@@ -117,6 +129,10 @@ public class Migrate16xTo220IT extends BaseIT {
 
     @Test
     public void checkMigratedData() throws Exception {
+        if (SEARCH_ENGINE_OPENSEARCH.equals(searchEngine)) {
+            System.out.println("Migration from 1.x to 2.x not supported for 
OpenSearch, skipping checks");
+            return;
+        }
         checkMergedProfilesAliases();
         checkProfileInterests();
         checkScopeHaveBeenCreated();
@@ -420,4 +436,5 @@ public class Migrate16xTo220IT extends BaseIT {
             Assert.assertEquals("eventTriggeredabcdefgh", 
pastEvents.get(0).get("key"));
             Assert.assertEquals(5, (int) pastEvents.get(0).get("count"));
         }
+
     }
diff --git a/itests/src/test/resources/org.apache.unomi.healthcheck.cfg 
b/itests/src/test/resources/org.apache.unomi.healthcheck.cfg
index 710876fe4..dadf50dd7 100644
--- a/itests/src/test/resources/org.apache.unomi.healthcheck.cfg
+++ b/itests/src/test/resources/org.apache.unomi.healthcheck.cfg
@@ -22,10 +22,17 @@ esLogin = ${org.apache.unomi.elasticsearch.username:-}
 esPassword = ${org.apache.unomi.elasticsearch.password:-}
 httpClient.trustAllCertificates = 
${org.apache.unomi.elasticsearch.sslTrustAllCertificates:-false}
 
+# OpenSearch configuration
+osAddresses = ${org.apache.unomi.opensearch.addresses:-localhost:9200}
+osSSLEnabled = ${org.apache.unomi.opensearch.sslEnable:-false}
+osLogin = ${org.apache.unomi.opensearch.username:-admin}
+osPassword = ${org.apache.unomi.opensearch.password:-}
+httpClient.trustAllCertificates = 
${org.apache.unomi.opensearch.sslTrustAllCertificates:-false}
+
 # Security configuration
 authentication.realm = ${org.apache.unomi.security.realm:-karaf}
 
 # Health check configuration
 healthcheck.enabled = ${org.apache.unomi.healthcheck.enabled:-true}
-healthcheck.providers = 
${org.apache.unomi.healthcheck.providers:-cluster,elasticsearch,unomi,persistence}
+healthcheck.providers = 
${org.apache.unomi.healthcheck.providers:-cluster,elasticsearch,opensearch,unomi,persistence}
 healthcheck.timeout = ${org.apache.unomi.healthcheck.timeout:-400}
diff --git 
a/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml 
b/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 9fceda381..25c371b6c 100644
--- a/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/lifecycle-watcher/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -41,7 +41,6 @@
                 <entry key="org.apache.unomi.scripting" value="false"/>
                 <entry key="org.apache.unomi.metrics" value="false"/>
                 <entry key="org.apache.unomi.persistence-spi" value="false"/>
-                <entry key="org.apache.unomi.persistence-elasticsearch-core" 
value="false"/>
                 <entry key="org.apache.unomi.services" value="false"/>
                 <entry key="org.apache.unomi.cxs-lists-extension-services" 
value="false"/>
                 <entry key="org.apache.unomi.cxs-lists-extension-rest" 
value="false"/>
diff --git 
a/persistence-opensearch/conditions/src/main/java/org/apache/unomi/persistence/opensearch/conditions/PastEventConditionOSQueryBuilder.java
 
b/persistence-opensearch/conditions/src/main/java/org/apache/unomi/persistence/opensearch/conditions/PastEventConditionOSQueryBuilder.java
index fb6e0d9e6..28d558ba8 100644
--- 
a/persistence-opensearch/conditions/src/main/java/org/apache/unomi/persistence/opensearch/conditions/PastEventConditionOSQueryBuilder.java
+++ 
b/persistence-opensearch/conditions/src/main/java/org/apache/unomi/persistence/opensearch/conditions/PastEventConditionOSQueryBuilder.java
@@ -20,9 +20,9 @@ package org.apache.unomi.persistence.opensearch.conditions;
 import org.apache.unomi.api.Event;
 import org.apache.unomi.api.Profile;
 import org.apache.unomi.api.conditions.Condition;
-import org.apache.unomi.api.conditions.ConditionType;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.SegmentService;
+import org.apache.unomi.api.utils.ConditionBuilder;
 import org.apache.unomi.persistence.opensearch.ConditionOSQueryBuilder;
 import 
org.apache.unomi.persistence.opensearch.ConditionOSQueryBuilderDispatcher;
 import org.apache.unomi.persistence.spi.PersistenceService;
@@ -89,7 +89,8 @@ public class PastEventConditionOSQueryBuilder implements 
ConditionOSQueryBuilder
             // TODO see for deprecation, this should not happen anymore each 
past event condition should have a generatedPropertyKey
             Condition eventCondition = getEventCondition(condition, context, 
null, definitionsService, scriptExecutor);
             Set<String> ids = getProfileIdsMatchingEventCount(eventCondition, 
minimumEventCount, maximumEventCount);
-            return dispatcher.buildFilter(getProfileIdsCondition(ids, 
eventsOccurred), context);
+            ConditionBuilder conditionBuilder = 
definitionsService.getConditionBuilder();
+            return 
dispatcher.buildFilter(conditionBuilder.condition("idsCondition").parameter("ids",
 ids).parameter("match", eventsOccurred).build(), context);
         }
     }
 
@@ -112,7 +113,8 @@ public class PastEventConditionOSQueryBuilder implements 
ConditionOSQueryBuilder
             }
 
             Set<String> profileIds = 
getProfileIdsMatchingEventCount(eventCondition, minimumEventCount, 
maximumEventCount);
-            return eventsOccurred ? profileIds.size() : 
persistenceService.queryCount(getProfileIdsCondition(profileIds, false), 
Profile.ITEM_TYPE);
+            ConditionBuilder conditionBuilder = 
definitionsService.getConditionBuilder();
+            return eventsOccurred ? profileIds.size() : 
persistenceService.queryCount(conditionBuilder.condition("idsCondition").parameter("ids",
 profileIds).parameter("match", false).build(), Profile.ITEM_TYPE);
         }
     }
 
@@ -123,44 +125,38 @@ public class PastEventConditionOSQueryBuilder implements 
ConditionOSQueryBuilder
         return operator == null || operator.equals("eventsOccurred");
     }
 
-    private Condition getProfileIdsCondition(Set<String> ids, boolean 
shouldMatch) {
-        Condition idsCondition = new Condition();
-        
idsCondition.setConditionType(definitionsService.getConditionType("idsCondition"));
-        idsCondition.setParameter("ids", ids);
-        idsCondition.setParameter("match", shouldMatch);
-        return idsCondition;
-    }
-
     private Condition getProfileConditionForCounter(String 
generatedPropertyKey, Integer minimumEventCount, Integer maximumEventCount, 
boolean eventsOccurred) {
-        String generatedPropertyName = "systemProperties.pastEvents." + 
generatedPropertyKey;
-        ConditionType profilePropertyConditionType = 
definitionsService.getConditionType("profilePropertyCondition");
         if (eventsOccurred) {
-            Condition counterIsBetweenBoundaries = new Condition();
-            
counterIsBetweenBoundaries.setConditionType(profilePropertyConditionType);
-            counterIsBetweenBoundaries.setParameter("propertyName", 
generatedPropertyName);
-            counterIsBetweenBoundaries.setParameter("comparisonOperator", 
"between");
-            counterIsBetweenBoundaries.setParameter("propertyValuesInteger", 
Arrays.asList(minimumEventCount, maximumEventCount));
-            return counterIsBetweenBoundaries;
+            return createEventOccurredCondition(generatedPropertyKey, 
minimumEventCount, maximumEventCount);
         } else {
-            Condition counterMissing = new Condition();
-            counterMissing.setConditionType(profilePropertyConditionType);
-            counterMissing.setParameter("propertyName", generatedPropertyName);
-            counterMissing.setParameter("comparisonOperator", "missing");
-
-            Condition counterZero = new Condition();
-            counterZero.setConditionType(profilePropertyConditionType);
-            counterZero.setParameter("propertyName", generatedPropertyName);
-            counterZero.setParameter("comparisonOperator", "equals");
-            counterZero.setParameter("propertyValueInteger", 0);
-
-            Condition counterCondition = new Condition();
-            
counterCondition.setConditionType(definitionsService.getConditionType("booleanCondition"));
-            counterCondition.setParameter("operator", "or");
-            counterCondition.setParameter("subConditions", 
Arrays.asList(counterMissing, counterZero));
-            return counterCondition;
+            return createEventNotOccurredCondition(generatedPropertyKey);
         }
     }
 
+    private Condition createEventOccurredCondition(String 
generatedPropertyKey, Integer minimumEventCount, Integer maximumEventCount) {
+        ConditionBuilder conditionBuilder = 
definitionsService.getConditionBuilder();
+        ConditionBuilder.ConditionItem subConditionCount = 
conditionBuilder.profileProperty("systemProperties.pastEvents.count").between(minimumEventCount,
 maximumEventCount);
+        ConditionBuilder.ConditionItem subConditionKey = 
conditionBuilder.profileProperty("systemProperties.pastEvents.key").equalTo(generatedPropertyKey);
+        ConditionBuilder.ConditionItem booleanCondition = 
conditionBuilder.and(subConditionCount, subConditionKey);
+        return conditionBuilder.nested(booleanCondition, 
"systemProperties.pastEvents").build();
+    }
+
+    private Condition createEventNotOccurredCondition(String 
generatedPropertyKey) {
+        ConditionBuilder.ConditionItem counterMissing = 
createPastEventMustNotExistCondition(generatedPropertyKey);
+        ConditionBuilder conditionBuilder = 
definitionsService.getConditionBuilder();
+        ConditionBuilder.ConditionItem counterZero = 
conditionBuilder.profileProperty("systemProperties.pastEvents.count").equalTo(0);
+        ConditionBuilder.ConditionItem keyEquals = 
conditionBuilder.profileProperty("systemProperties.pastEvents.key").equalTo(generatedPropertyKey);
+        ConditionBuilder.ConditionItem keyExistsAndCounterZero = 
conditionBuilder.and(counterZero, keyEquals);
+        ConditionBuilder.ConditionItem nestedKeyExistsAndCounterZero = 
conditionBuilder.nested(keyExistsAndCounterZero, "systemProperties.pastEvents");
+        return conditionBuilder.or(counterMissing, 
nestedKeyExistsAndCounterZero).build();
+    }
+
+    private ConditionBuilder.ConditionItem 
createPastEventMustNotExistCondition(String generatedPropertyKey) {
+        ConditionBuilder conditionBuilder = 
definitionsService.getConditionBuilder();
+        ConditionBuilder.ConditionItem keyEquals = 
conditionBuilder.profileProperty("systemProperties.pastEvents.key").equalTo(generatedPropertyKey);
+        return conditionBuilder.not(keyEquals);
+    }
+
     private Set<String> getProfileIdsMatchingEventCount(Condition 
eventCondition, int minimumEventCount, int maximumEventCount) {
         boolean noBoundaries = minimumEventCount == 1 && maximumEventCount == 
Integer.MAX_VALUE;
         if (pastEventsDisablePartitions) {
diff --git 
a/persistence-opensearch/core/src/main/resources/META-INF/cxs/mappings/profile.json
 
b/persistence-opensearch/core/src/main/resources/META-INF/cxs/mappings/profile.json
index 81cc14d0f..6e650a178 100644
--- 
a/persistence-opensearch/core/src/main/resources/META-INF/cxs/mappings/profile.json
+++ 
b/persistence-opensearch/core/src/main/resources/META-INF/cxs/mappings/profile.json
@@ -40,6 +40,13 @@
         }
       }
     },
+    "systemProperties": {
+      "properties": {
+        "pastEvents": {
+          "type": "nested"
+        }
+      }
+    },
     "consents": {
       "properties": {
         "statusDate": {
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/DateUtils.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/DateUtils.java
index eac3e358b..0268c2212 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/DateUtils.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/DateUtils.java
@@ -45,7 +45,7 @@ public class DateUtils {
                 return Date.from(instant);
             } catch (DateMathParseException e) {
                 LOGGER.warn("unable to parse date. See debug log level for 
full stacktrace");
-                LOGGER.debug("unable to parse date {}", value, e);
+                LOGGER.warn("unable to parse date {}", value, e);
             }
             return null;
         }
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java
index 95feb6994..4c599c1c9 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParser.java
@@ -75,7 +75,18 @@ public class DateMathParser {
         this.roundUpFormatter = roundUpFormatter;
     }
 
+    private String normalizeDateMathInput(String input) {
+        // Replace 't' with 'T' only when it's part of an ISO datetime format 
(e.g., `2022-05-18t15:23:17z`)
+        input = input.replaceAll("(?<=\\d{4}-\\d{2}-\\d{2})t", "T"); // Match 
't' after a full date
+        // Replace 'z' with 'Z' only when it's at the end of the string or 
follows time components
+        input = input.replaceAll("z$", "Z"); // Match 'z' at the end
+        input = input.replaceAll("(?<=[:\\d])z", "Z"); // Match 'z' after a 
time component
+        return input;
+    }
+
     public Instant parse(String text, LongSupplier now, boolean 
roundUpProperty, ZoneId timeZone) {
+        text = text.trim();
+
         Instant time;
         String mathString;
         if (text.startsWith("now")) {
@@ -89,10 +100,12 @@ public class DateMathParser {
             int index = text.indexOf("||");
             if (index == -1) {
                 // no math, just parse date
+                // Normalize input for case-insensitive ISO datetime handling
+                text = normalizeDateMathInput(text);
                 return parseDateTime(text, timeZone, roundUpProperty);
             }
-            time = parseDateTime(text.substring(0, index), timeZone, false);
-            mathString = text.substring(index + 2);
+            time = parseDateTime(normalizeDateMathInput(text.substring(0, 
index).trim()), timeZone, false);
+            mathString = text.substring(index + 2).trim();
         }
 
         return parseMath(mathString, time, roundUpProperty, timeZone);
diff --git 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java
 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java
index ab2616bf1..bc21e3908 100644
--- 
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java
+++ 
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatter.java
@@ -53,6 +53,15 @@ public class JavaDateFormatter {
         this.allowEpochSecond = epochSecond;
     }
 
+    private String adjustForCaseInsensitive(String input) {
+        // Replace 't' with 'T' only when it's part of an ISO datetime format 
(e.g., `2022-05-18t15:23:17z`)
+        input = input.replaceAll("(?<=\\d{4}-\\d{2}-\\d{2})t", "T"); // Match 
't' after a full date
+        // Replace 'z' with 'Z' only when it's at the end of the string or 
follows time components
+        input = input.replaceAll("z$", "Z"); // Match 'z' at the end
+        input = input.replaceAll("(?<=[:\\d])z", "Z"); // Match 'z' after a 
time component
+        return input;
+    }
+
     public TemporalAccessor parse(String input) {
         // Numeric check
         if (isNumeric(input)) {
@@ -65,6 +74,8 @@ public class JavaDateFormatter {
             }
         }
 
+        input = adjustForCaseInsensitive(input);
+
         for (FormatDefinition def : formats) {
             try {
                 String adjusted = adjustForPattern(input, def.pattern);
@@ -303,6 +314,7 @@ public class JavaDateFormatter {
     private FormatDefinition fmt(String name, String pattern) {
         // Apply UTC zone to all and consider using strict resolver if needed
         DateTimeFormatter dtf = new DateTimeFormatterBuilder()
+                .parseCaseSensitive()
                 .appendPattern(pattern)
                 .toFormatter()
                 .withZone(ZoneOffset.UTC);
diff --git 
a/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParserTest.java
 
b/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParserTest.java
index 7882a3bff..348523a59 100644
--- 
a/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParserTest.java
+++ 
b/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/DateMathParserTest.java
@@ -189,4 +189,54 @@ public class DateMathParserTest {
             assertTrue(e.getMessage().startsWith("failed to parse date field 
[not-a-date] with format"));
         }
     }
+
+    @Test
+    public void testInvalidLowercaseMathOperator() {
+        try {
+            parser.parse("now*1d", fixedNow, false, ZoneOffset.UTC); // 
Invalid operator
+            fail("Expected an exception");
+        } catch (DateMathParseException e) {
+            assertEquals("operator not supported for date math [*1d]", 
e.getMessage());
+        }
+    }
+
+
+    @Test
+    public void testDateMathWithCaseInsensitiveParsing() {
+        Instant parsed = parser.parse("2001-01-01t12:00:00z||+1d", fixedNow, 
false, ZoneOffset.UTC);
+        assertEquals("2001-01-02T12:00:00Z", parsed.toString());
+
+        parsed = parser.parse("now+1h/d", fixedNow, false, ZoneOffset.UTC);
+        assertEquals("2001-01-01T00:00:00Z", parsed.toString());
+    }
+
+    @Test
+    public void testMixedCaseDateMath() {
+        Instant parsed = parser.parse("2001-01-01T12:00:00z||+1M/d", fixedNow, 
true, ZoneOffset.UTC); // Mixed case
+        assertEquals("2001-02-01T23:59:59.999Z", parsed.toString());
+    }
+
+    @Test
+    public void testInvalidMathWithCaseInsensitiveInput() {
+        try {
+            parser.parse("now*1d", fixedNow, false, ZoneOffset.UTC); // 
Invalid operator
+            fail("Expected an exception");
+        } catch (DateMathParseException e) {
+            assertEquals("operator not supported for date math [*1d]", 
e.getMessage());
+        }
+
+        try {
+            parser.parse("2001-01-01t12:00:00x||+1d", fixedNow, false, 
ZoneOffset.UTC); // Invalid separator
+            fail("Expected an exception");
+        } catch (DateMathParseException e) {
+            assertTrue(e.getMessage().contains("failed to parse date field"));
+        }
+    }
+
+    @Test
+    public void testDateMathWithExtraSpaces() {
+        Instant parsed = parser.parse("  2001-01-01T12:00:00Z  || +1d  ", 
fixedNow, false, ZoneOffset.UTC);
+        assertEquals("2001-01-02T12:00:00Z", parsed.toString());
+    }
+
 }
diff --git 
a/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatterTest.java
 
b/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatterTest.java
index 76a52c79e..e43ad7c30 100644
--- 
a/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatterTest.java
+++ 
b/persistence-spi/src/test/java/org/apache/unomi/persistence/spi/conditions/datemath/JavaDateFormatterTest.java
@@ -159,6 +159,45 @@ public class JavaDateFormatterTest {
         }
     }
 
-    // Add more tests as needed for strict variants, time_no_millis, t_time, 
week_date, etc.
-    // The provided tests give a broad coverage of different formats.
+    @Test
+    public void testMixedCaseDate() {
+        JavaDateFormatter formatter = new 
JavaDateFormatter("strict_date_optional_time");
+        Instant parsed = 
Instant.from(formatter.parse("2022-05-18T15:23:17z")); // mixed case 'T' and 'z'
+        assertEquals("2022-05-18T15:23:17Z", parsed.toString());
+    }
+
+    @Test
+    public void testCaseInsensitiveISOWithValidInputs() {
+        JavaDateFormatter formatter = new 
JavaDateFormatter("strict_date_optional_time");
+        // Lowercase `t` and `z`
+        Instant parsed = Instant.from(formatter.parse("2022-05-18t15:23:17z"));
+        assertEquals("2022-05-18T15:23:17Z", parsed.toString());
+
+        // Mixed case
+        parsed = Instant.from(formatter.parse("2022-05-18T15:23:17z"));
+        assertEquals("2022-05-18T15:23:17Z", parsed.toString());
+
+        // Uppercase (valid)
+        parsed = Instant.from(formatter.parse("2022-05-18T15:23:17Z"));
+        assertEquals("2022-05-18T15:23:17Z", parsed.toString());
+    }
+
+    @Test
+    public void testCaseInsensitiveISOWithInvalidInputs() {
+        JavaDateFormatter formatter = new 
JavaDateFormatter("strict_date_optional_time");
+
+        try {
+            formatter.parse("2022-05-18x15:23:17z"); // Invalid separator
+            fail("Expected an exception");
+        } catch (DateMathParseException e) {
+            assertTrue(e.getMessage().contains("failed to parse date field"));
+        }
+
+        try {
+            formatter.parse("2022-05-18T15:23:17X"); // Invalid character for 
timezone
+            fail("Expected an exception");
+        } catch (DateMathParseException e) {
+            assertTrue(e.getMessage().contains("failed to parse date field"));
+        }
+    }
 }

Reply via email to