This is an automated email from the ASF dual-hosted git repository.
tkobayas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git
The following commit(s) were added to refs/heads/main by this push:
new 2f6090adca [incubator-kie-drools-6733] Preserve insertion order in
ListDataStore (#6734)
2f6090adca is described below
commit 2f6090adcaf9184203ccd44af8b1a4a38a46fec7
Author: Toshiya Kobayashi <[email protected]>
AuthorDate: Fri May 29 17:00:40 2026 +0900
[incubator-kie-drools-6733] Preserve insertion order in ListDataStore
(#6734)
ListDataStore used IdentityHashMap which does not preserve insertion
order, causing subscribe() to replay events in arbitrary order and
breaking sliding window CEP behavior. Added an ArrayList<DataHandle>
alongside the IdentityHashMap to maintain insertion order for iteration
and replay while preserving identity-based lookup semantics.
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
---
.../ruleunits/impl/datasources/ListDataStore.java | 11 +-
.../drools/ruleunits/impl/DataStoreOrderUnit.java | 48 +++++++
.../drools/ruleunits/impl/ListDataStoreTest.java | 143 +++++++++++++++++++++
.../org/drools/ruleunits/impl/DataStoreOrder.drl | 27 ++++
4 files changed, 227 insertions(+), 2 deletions(-)
diff --git
a/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
b/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
index 5134c3b48c..be76aaea1e 100644
---
a/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
+++
b/drools-ruleunits/drools-ruleunits-impl/src/main/java/org/drools/ruleunits/impl/datasources/ListDataStore.java
@@ -18,8 +18,10 @@
*/
package org.drools.ruleunits.impl.datasources;
+import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import org.drools.base.definitions.rule.impl.RuleImpl;
@@ -38,19 +40,22 @@ import org.kie.api.runtime.rule.RuleContext;
public class ListDataStore<T> extends AbstractDataSource<T> implements
Iterable<T>, DataStore<T>, InternalStoreCallback {
private final Map<T, DataHandle> store = new IdentityHashMap<>();
+ private final List<DataHandle> insertionOrder = new ArrayList<>();
protected ListDataStore() {
}
@Override
+ @SuppressWarnings("unchecked")
public Iterator<T> iterator() {
- return store.keySet().iterator();
+ return insertionOrder.stream().map(dh -> (T)
dh.getObject()).iterator();
}
public DataHandle add(T t) {
DataHandle dh = createDataHandle(t);
store.put(t, dh);
+ insertionOrder.add(dh);
forEachSubscriber(s -> internalInsert(dh, s));
return dh;
}
@@ -89,12 +94,13 @@ public class ListDataStore<T> extends AbstractDataSource<T>
implements Iterable
public void remove(DataHandle handle) {
forEachSubscriber(s -> s.delete(handle));
store.remove(handle.getObject());
+ insertionOrder.remove(handle);
}
@Override
public void subscribe(DataProcessor processor) {
super.subscribe(processor);
- store.values().forEach(dh -> internalInsert(dh, processor));
+ insertionOrder.forEach(dh -> internalInsert(dh, processor));
}
@Override
@@ -129,6 +135,7 @@ public class ListDataStore<T> extends AbstractDataSource<T>
implements Iterable
entryPointSubscribers.forEach(s -> s.delete(dh, rule, terminalNode,
fhState));
subscribers.forEach(s -> s.delete(dh));
store.remove(fh.getObject());
+ insertionOrder.remove(dh);
}
private void internalInsert(DataHandle dh, DataProcessor s) {
diff --git
a/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/DataStoreOrderUnit.java
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/DataStoreOrderUnit.java
new file mode 100644
index 0000000000..5e0477ff2d
--- /dev/null
+++
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/DataStoreOrderUnit.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.ruleunits.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.drools.ruleunits.api.DataSource;
+import org.drools.ruleunits.api.DataStore;
+import org.drools.ruleunits.api.RuleUnitData;
+
+public class DataStoreOrderUnit implements RuleUnitData {
+
+ private final DataStore<String> strings;
+ private final List<String> results = new ArrayList<>();
+
+ public DataStoreOrderUnit() {
+ this(DataSource.createStore());
+ }
+
+ public DataStoreOrderUnit(DataStore<String> strings) {
+ this.strings = strings;
+ }
+
+ public DataStore<String> getStrings() {
+ return strings;
+ }
+
+ public List<String> getResults() {
+ return results;
+ }
+}
diff --git
a/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/ListDataStoreTest.java
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/ListDataStoreTest.java
new file mode 100644
index 0000000000..5c0016f900
--- /dev/null
+++
b/drools-ruleunits/drools-ruleunits-impl/src/test/java/org/drools/ruleunits/impl/ListDataStoreTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.ruleunits.impl;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.drools.ruleunits.api.DataHandle;
+import org.drools.ruleunits.api.DataProcessor;
+import org.drools.ruleunits.api.DataSource;
+import org.drools.ruleunits.api.DataStore;
+import org.drools.ruleunits.api.RuleUnitInstance;
+import org.drools.ruleunits.api.RuleUnitProvider;
+import org.drools.ruleunits.api.conf.RuleConfig;
+import org.junit.jupiter.api.Test;
+import org.kie.api.event.rule.ObjectDeletedEvent;
+import org.kie.api.event.rule.ObjectInsertedEvent;
+import org.kie.api.event.rule.ObjectUpdatedEvent;
+import org.kie.api.event.rule.RuleRuntimeEventListener;
+import org.kie.api.runtime.rule.FactHandle;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ListDataStoreTest {
+
+ @Test
+ public void subscribeReplaysInInsertionOrder() {
+ DataStore<String> store = DataSource.createStore();
+ store.add("A");
+ store.add("B");
+ store.add("C");
+ store.add("D");
+ store.add("E");
+
+ RecordingProcessor<String> recorder = new RecordingProcessor<>();
+ store.subscribe(recorder);
+
+ assertThat(recorder.insertedObjects).containsExactly("A", "B", "C",
"D", "E");
+ }
+
+ @Test
+ public void iteratorReturnsInsertionOrder() {
+ DataStore<String> store = DataSource.createStore();
+ store.add("X");
+ store.add("Y");
+ store.add("Z");
+
+ List<String> iterated = new ArrayList<>();
+ for (String s : (Iterable<String>) store) {
+ iterated.add(s);
+ }
+
+ assertThat(iterated).containsExactly("X", "Y", "Z");
+ }
+
+ @Test
+ public void subscribeAfterRemovePreservesOrder() {
+ DataStore<String> store = DataSource.createStore();
+ DataHandle dhA = store.add("A");
+ store.add("B");
+ DataHandle dhC = store.add("C");
+ store.add("D");
+
+ store.remove(dhA);
+ store.remove(dhC);
+
+ RecordingProcessor<String> recorder = new RecordingProcessor<>();
+ store.subscribe(recorder);
+
+ assertThat(recorder.insertedObjects).containsExactly("B", "D");
+ }
+
+ @Test
+ public void ruleUnitDataStoreReplayOrder() {
+ DataStoreOrderUnit unit = new DataStoreOrderUnit();
+ unit.getStrings().add("A");
+ unit.getStrings().add("B");
+ unit.getStrings().add("C");
+ unit.getStrings().add("D");
+ unit.getStrings().add("E");
+
+ List<String> insertedObjects = new ArrayList<>();
+
+ RuleConfig ruleConfig = RuleUnitProvider.get().newRuleConfig();
+ ruleConfig.getRuleRuntimeListeners().add(new
RuleRuntimeEventListener() {
+ @Override
+ public void objectInserted(ObjectInsertedEvent event) {
+ if (event.getObject() instanceof String) {
+ insertedObjects.add((String) event.getObject());
+ }
+ }
+
+ @Override
+ public void objectUpdated(ObjectUpdatedEvent event) {
+ }
+
+ @Override
+ public void objectDeleted(ObjectDeletedEvent event) {
+ }
+ });
+
+ try (RuleUnitInstance<DataStoreOrderUnit> instance =
+ RuleUnitProvider.get().createRuleUnitInstance(unit,
ruleConfig)) {
+ assertThat(insertedObjects).containsExactly("A", "B", "C", "D",
"E");
+ }
+ }
+
+ private static class RecordingProcessor<T> implements DataProcessor<T> {
+
+ final List<T> insertedObjects = new ArrayList<>();
+
+ @Override
+ public FactHandle insert(DataHandle handle, T object) {
+ insertedObjects.add(object);
+ return null;
+ }
+
+ @Override
+ public void update(DataHandle handle, T object) {
+ }
+
+ @Override
+ public void delete(DataHandle handle) {
+ }
+ }
+}
diff --git
a/drools-ruleunits/drools-ruleunits-impl/src/test/resources/org/drools/ruleunits/impl/DataStoreOrder.drl
b/drools-ruleunits/drools-ruleunits-impl/src/test/resources/org/drools/ruleunits/impl/DataStoreOrder.drl
new file mode 100644
index 0000000000..d2dbfc46c5
--- /dev/null
+++
b/drools-ruleunits/drools-ruleunits-impl/src/test/resources/org/drools/ruleunits/impl/DataStoreOrder.drl
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.ruleunits.impl;
+unit DataStoreOrderUnit;
+
+rule "RecordEach"
+when
+ $s: /strings
+then
+ results.add($s);
+end
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]