From 3fd51b32065d0402368b07097705b5c45b0e7fc5 Mon Sep 17 00:00:00 2001 From: nprentza <90831697+nprentza@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:52:31 +0300 Subject: [PATCH] [DROOLS-7512] opt-in object store with references for reliable session (#5445) * PersistenceObjectStrategy option, basic test 'insert-failover-shouldFireRules' * Copyright header added * addressing draft PR comments, improved test method * add global results in testPhase1FailoverPhase2Phase3_ShouldFireRules * init uniqueObjectTypesInStore * more tests, other improvements * sonar improvements * improvements * improvements: switch, groupingBy --- .../core/ReliableNamedEntryPoint.java | 3 +- .../core/SerializableStoredObject.java | 2 +- .../core/SerializableStoredRefObject.java | 54 ++++ .../SimpleReliableObjectStoreFactory.java | 3 +- ...impleSerializationReliableObjectStore.java | 12 +- ...rializationReliableObjectStoreFactory.java | 9 + ...leSerializationReliableRefObjectStore.java | 138 +++++++++ ...eInfinispanReliableObjectStoreFactory.java | 14 +- .../test/ReliabilityFireAndAlarmTest.java | 274 ++++++++++++++++++ .../test/ReliabilityTestBasics.java | 56 +++- .../org/test/domain/fireandalarm/Alarm.java | 21 ++ .../org/test/domain/fireandalarm/Fire.java | 36 +++ .../org/test/domain/fireandalarm/Room.java | 42 +++ .../test/domain/fireandalarm/Sprinkler.java | 50 ++++ .../runtime/conf/PersistedSessionOption.java | 13 + 15 files changed, 706 insertions(+), 21 deletions(-) create mode 100644 drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredRefObject.java create mode 100644 drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableRefObjectStore.java create mode 100644 drools-reliability/drools-reliability-tests/src/test/java/org/drools/reliability/test/ReliabilityFireAndAlarmTest.java create mode 100644 drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Alarm.java create mode 100644 drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Fire.java create mode 100644 drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Room.java create mode 100644 drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Sprinkler.java diff --git a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/ReliableNamedEntryPoint.java b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/ReliableNamedEntryPoint.java index d747a504051..9f9ae720ea6 100644 --- a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/ReliableNamedEntryPoint.java +++ b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/ReliableNamedEntryPoint.java @@ -33,7 +33,8 @@ public ReliableNamedEntryPoint(EntryPointId entryPoint, EntryPointNode entryPoin protected ObjectStore createObjectStore(EntryPointId entryPoint, RuleBaseConfiguration conf, ReteEvaluator reteEvaluator) { boolean storesOnlyStrategy = reteEvaluator.getSessionConfiguration().getPersistedSessionOption().getPersistenceStrategy() == PersistedSessionOption.PersistenceStrategy.STORES_ONLY; return storesOnlyStrategy ? - SimpleReliableObjectStoreFactory.get().createSimpleReliableObjectStore(StorageManagerFactory.get().getStorageManager().getOrCreateStorageForSession(reteEvaluator, "ep" + getEntryPointId())) : + SimpleReliableObjectStoreFactory.get().createSimpleReliableObjectStore(StorageManagerFactory.get().getStorageManager().getOrCreateStorageForSession(reteEvaluator, "ep" + getEntryPointId()), + reteEvaluator.getSessionConfiguration().getPersistedSessionOption()) : new FullReliableObjectStore(StorageManagerFactory.get().getStorageManager().getOrCreateStorageForSession(reteEvaluator, "ep" + getEntryPointId())); } diff --git a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredObject.java b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredObject.java index 9495d5720a3..57a6c3c2702 100644 --- a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredObject.java +++ b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredObject.java @@ -19,7 +19,7 @@ public class SerializableStoredObject extends BaseStoredObject { - private final Serializable object; + protected final Serializable object; public SerializableStoredObject(Object object, boolean propagated) { super(propagated); diff --git a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredRefObject.java b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredRefObject.java new file mode 100644 index 00000000000..7093434a763 --- /dev/null +++ b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SerializableStoredRefObject.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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.reliability.core; + +import org.drools.core.common.Storage; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class SerializableStoredRefObject extends SerializableStoredObject { + + private final Map referencedObjects; + + public SerializableStoredRefObject(Object object, boolean propagated) { + super(object, propagated); + referencedObjects=new HashMap<>(); + } + + public void addReferencedObject(String fieldName, Long refObjectKey){ + this.referencedObjects.put(fieldName, refObjectKey); + } + + public StoredObject updateReferencedObjects(Storage storage){ + this.referencedObjects.keySet().forEach(fieldName -> { + Optional refField = Arrays.stream(object.getClass().getDeclaredFields()) + .filter(f -> f.getName().equals(fieldName)).findFirst(); + if (refField.isPresent()){ + refField.get().setAccessible(true); + try { + refField.get().set(this.object, storage.get(this.referencedObjects.get(refField.get().getName())).getObject()); + } catch (IllegalAccessException e) { + throw new ReliabilityRuntimeException(e); + } + } + }); + return this; + } +} diff --git a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleReliableObjectStoreFactory.java b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleReliableObjectStoreFactory.java index 3f195361382..188f90610c9 100644 --- a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleReliableObjectStoreFactory.java +++ b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleReliableObjectStoreFactory.java @@ -17,10 +17,11 @@ import org.drools.core.common.Storage; import org.kie.api.internal.utils.KieService; +import org.kie.api.runtime.conf.PersistedSessionOption; public interface SimpleReliableObjectStoreFactory extends KieService { - SimpleReliableObjectStore createSimpleReliableObjectStore(Storage storage); + SimpleReliableObjectStore createSimpleReliableObjectStore(Storage storage, PersistedSessionOption persistedSessionOption); class Holder { diff --git a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStore.java b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStore.java index 24acb1c5e6d..71618647ee9 100644 --- a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStore.java +++ b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStore.java @@ -15,10 +15,6 @@ package org.drools.reliability.core; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - import org.drools.core.ClockType; import org.drools.core.common.DefaultEventHandle; import org.drools.core.common.IdentityObjectStore; @@ -27,9 +23,13 @@ import org.drools.core.common.InternalWorkingMemoryEntryPoint; import org.drools.core.common.Storage; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + public class SimpleSerializationReliableObjectStore extends IdentityObjectStore implements SimpleReliableObjectStore { - protected final transient Storage storage; + protected transient Storage storage; protected boolean reInitPropagated = false; @@ -113,7 +113,7 @@ public void putIntoPersistedStorage(InternalFactHandle handle, boolean propagate storage.put(getHandleForObject(object).getId(), storedObject); } - private StoredObject factHandleToStoredObject(InternalFactHandle handle, boolean propagated, Object object) { + protected StoredObject factHandleToStoredObject(InternalFactHandle handle, boolean propagated, Object object) { return handle.isEvent() ? createStoredEvent(propagated, object, ((DefaultEventHandle) handle).getStartTimestamp(), ((DefaultEventHandle) handle).getDuration()) : createStoredObject(propagated, object); diff --git a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStoreFactory.java b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStoreFactory.java index 81478de918b..1b39a7818c5 100644 --- a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStoreFactory.java +++ b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableObjectStoreFactory.java @@ -16,6 +16,7 @@ package org.drools.reliability.core; import org.drools.core.common.Storage; +import org.kie.api.runtime.conf.PersistedSessionOption; public class SimpleSerializationReliableObjectStoreFactory implements SimpleReliableObjectStoreFactory { @@ -25,6 +26,14 @@ public SimpleReliableObjectStore createSimpleReliableObjectStore(Storage storage, PersistedSessionOption persistedSessionOption) { + switch (persistedSessionOption.getPersistenceObjectsStrategy()){ + case SIMPLE: return new SimpleSerializationReliableObjectStore(storage); + case OBJECT_REFERENCES: return new SimpleSerializationReliableRefObjectStore(storage); + default: throw new UnsupportedOperationException(); + } + } + @Override public int servicePriority() { return servicePriorityValue; diff --git a/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableRefObjectStore.java b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableRefObjectStore.java new file mode 100644 index 00000000000..82328a4bc60 --- /dev/null +++ b/drools-reliability/drools-reliability-core/src/main/java/org/drools/reliability/core/SimpleSerializationReliableRefObjectStore.java @@ -0,0 +1,138 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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.reliability.core; + +import org.drools.core.common.InternalFactHandle; +import org.drools.core.common.Storage; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class SimpleSerializationReliableRefObjectStore extends SimpleSerializationReliableObjectStore { + + private Map uniqueObjectTypesInStore; // object type name, occurances + + public SimpleSerializationReliableRefObjectStore(Storage storage) { + super(storage); + uniqueObjectTypesInStore = new HashMap<>(); + if (storage.isEmpty()) { + this.storage = storage; + } else { + updateObjectTypesList(); + this.storage = updateObjectReferences(storage); + } + } + + private Storage updateObjectReferences(Storage storage) { + Storage updateStorage = storage; + + for (Long key : storage.keySet()) { + updateStorage.put(key, ((SerializableStoredRefObject) storage.get(key)).updateReferencedObjects(storage)); + } + return updateStorage; + } + + @Override + public void putIntoPersistedStorage(InternalFactHandle handle, boolean propagated) { + Object object = handle.getObject(); + StoredObject storedObject = factHandleToStoredObject(handle, reInitPropagated || propagated, object); + storage.put(getHandleForObject(object).getId(), setReferencedObjects(storedObject)); + // also add the type of the object into the uniqueObjectTypesInStore list (if not already there) + this.updateObjectTypesList(object); + } + + @Override + public void removeFromPersistedStorage(Object object) { + super.removeFromPersistedStorage(object); + // also remove instance from uniqueObjectTypesInStore + this.updateObjectTypesList(object); + } + + @Override + protected StoredObject createStoredObject(boolean propagated, Object object) { + return new SerializableStoredRefObject(object, propagated); + } + + private StoredObject setReferencedObjects(StoredObject object) { + List referencedObjects = getReferencedObjects(object.getObject()); + if (!referencedObjects.isEmpty()) { + // for each referenced object in sObject + // lookup in storage, find the object of reference, get its fact handle id + // save this association in the StoredObject + referencedObjects.forEach(field -> { + field.setAccessible(true); + Object fieldObject = null; + try { + fieldObject = field.get(object.getObject()); + } catch (IllegalAccessException e) { + throw new ReliabilityRuntimeException(e); + } + Long objectKey = fromObjectToFactHandleId(fieldObject); + if (objectKey != null) { + ((SerializableStoredRefObject) object).addReferencedObject(field.getName(), objectKey); + } + }); + } + return object; + } + + private Long fromObjectToFactHandleId(Object object) { + for (Long key : this.storage.keySet()) { + if (((SerializableStoredRefObject) storage.get(key)).getObject() == object) { + return key; + } + } + return null; + } + + private List getReferencedObjects(Object object) { + Field[] fields = object.getClass().getDeclaredFields(); + + return Arrays.stream(fields) + .filter(field -> uniqueObjectTypesInStore.containsKey(field.getType().getName())) + .collect(Collectors.toList()); + } + + private void updateObjectTypesList(Object object) { + uniqueObjectTypesInStore.put(object.getClass().getName(), + storage.values().stream().filter(sObject -> sObject.getObject().getClass().equals(object.getClass())).count()); + // if count==0 then remove entry + if (uniqueObjectTypesInStore.get(object.getClass().getName()) == 0) { + uniqueObjectTypesInStore.remove(object.getClass().getName()); + } + } + + private void updateObjectTypesList() { + // list of unique object types in storage + Set uTypeNames = new HashSet(); + storage.values().forEach(sObject -> { + uTypeNames.add(sObject.getObject().getClass().getName()); + }); + // add unique object types + their occurrences in the uniqueObjectTypesInStore + uniqueObjectTypesInStore.putAll(storage.values().stream() + .map(sObject -> sObject.getObject().getClass().getName()) + .filter(uTypeNames::contains) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))); + } +} + diff --git a/drools-reliability/drools-reliability-infinispan/src/main/java/org/drools/reliability/infinispan/SimpleInfinispanReliableObjectStoreFactory.java b/drools-reliability/drools-reliability-infinispan/src/main/java/org/drools/reliability/infinispan/SimpleInfinispanReliableObjectStoreFactory.java index 5a28d66e6db..49ae4f3be96 100644 --- a/drools-reliability/drools-reliability-infinispan/src/main/java/org/drools/reliability/infinispan/SimpleInfinispanReliableObjectStoreFactory.java +++ b/drools-reliability/drools-reliability-infinispan/src/main/java/org/drools/reliability/infinispan/SimpleInfinispanReliableObjectStoreFactory.java @@ -19,9 +19,11 @@ import org.drools.reliability.core.SimpleReliableObjectStore; import org.drools.reliability.core.SimpleReliableObjectStoreFactory; import org.drools.reliability.core.SimpleSerializationReliableObjectStore; +import org.drools.reliability.core.SimpleSerializationReliableRefObjectStore; import org.drools.reliability.core.StorageManagerFactory; import org.drools.reliability.core.StoredObject; import org.drools.reliability.infinispan.proto.SimpleProtoStreamReliableObjectStore; +import org.kie.api.runtime.conf.PersistedSessionOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,13 +33,18 @@ public class SimpleInfinispanReliableObjectStoreFactory implements SimpleReliabl private static final Logger LOG = LoggerFactory.getLogger(SimpleInfinispanReliableObjectStoreFactory.class); - public SimpleReliableObjectStore createSimpleReliableObjectStore(Storage storage) { + public SimpleReliableObjectStore createSimpleReliableObjectStore(Storage storage, PersistedSessionOption persistedSessionOption) { if (((InfinispanStorageManager)StorageManagerFactory.get().getStorageManager()).isProtoStream()) { LOG.debug("Using SimpleProtoStreamReliableObjectStore"); return new SimpleProtoStreamReliableObjectStore(storage); } else { - LOG.debug("Using SimpleSerializationReliableObjectStore"); - return new SimpleSerializationReliableObjectStore(storage); + if (persistedSessionOption.getPersistenceObjectsStrategy()== PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES){ + LOG.debug("Using SimpleSerializationReliableRefObjectStore"); + return new SimpleSerializationReliableRefObjectStore(storage); + }else{ + LOG.debug("Using SimpleSerializationReliableObjectStore"); + return new SimpleSerializationReliableObjectStore(storage); + } } } @@ -45,4 +52,5 @@ public SimpleReliableObjectStore createSimpleReliableObjectStore(Storage sprinklerR = getObjectByType(Sprinkler.class); + assertThat(sprinklerR.isEmpty()).isFalse(); + assertThat(((Sprinkler) sprinklerR.get()).isOn()).isTrue(); + } + + @ParameterizedTest + @MethodSource("strategyProviderStoresOnlyWithExplicitSafepoints") // does not support PersistedSessionOption.PersistenceObjectsStrategy.SIMPLE + void testPhase1FailoverPhase2Phase3_ShouldFireRules(PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy) { + createSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + + // phase 1 + Room room1 = new Room("Room 1"); + insert(room1); + insert(new Fire(room1)); + + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Raise alarm rule"); + + Optional alarm = getObjectByType(Alarm.class); + assertThat(alarm.isEmpty()).isFalse(); + + failover(); + restoreSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + clearResults(); + + // phase 2 + Optional room = getObjectByType(Room.class); + assertThat(room.isEmpty()).isFalse(); + Sprinkler sprinkler1 = new Sprinkler((Room) room.get()); + insert(sprinkler1); + + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Turn on sprinkler rule"); + clearResults(); + + Optional sprinkler = getObjectByType(Sprinkler.class); + assertThat(sprinkler.isEmpty()).isFalse(); + assertThat(((Sprinkler) sprinkler.get()).isOn()).isTrue(); + + // phase 3 + Optional fireFh = getFactHandleByType(Fire.class); + assertThat(fireFh.isEmpty()).isFalse(); + delete(fireFh.get()); + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Cancel alarm rule"); + } + + @ParameterizedTest + @MethodSource("strategyProviderStoresOnlyWithExplicitSafepoints") // does not support PersistedSessionOption.PersistenceObjectsStrategy.SIMPLE + void testPhase1FailoverPhase2FailoverPhase3_ShouldFireRules(PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy) { + createSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + + // phase 1 + Room room1 = new Room("Room 1"); + insert(room1); + insert(new Fire(room1)); + + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Raise alarm rule"); + + Optional alarm = getObjectByType(Alarm.class); + assertThat(alarm.isEmpty()).isFalse(); + + failover(); + restoreSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + clearResults(); + + // phase 2 + Optional room = getObjectByType(Room.class); + assertThat(room.isEmpty()).isFalse(); + Sprinkler sprinkler1 = new Sprinkler((Room) room.get()); + insert(sprinkler1); + + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Turn on sprinkler rule"); + clearResults(); + + Optional sprinkler = getObjectByType(Sprinkler.class); + assertThat(sprinkler.isEmpty()).isFalse(); + assertThat(((Sprinkler) sprinkler.get()).isOn()).isTrue(); + + failover(); + restoreSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + clearResults(); + + // phase 3 + Optional fireFh = getFactHandleByType(Fire.class); + assertThat(fireFh.isEmpty()).isFalse(); + delete(fireFh.get()); + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Cancel alarm rule"); + } + + @ParameterizedTest + @MethodSource("strategyProviderStoresOnlyWithExplicitSafepoints") // does not support PersistedSessionOption.PersistenceObjectsStrategy.SIMPLE + void testPhase1Phase2FailoverPhase3_ShouldFireRules(PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy) { + createSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + + // phase 1 + Room room1 = new Room("Room 1"); + insert(room1); + insert(new Fire(room1)); + + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Raise alarm rule"); + clearResults(); + + Optional alarm = getObjectByType(Alarm.class); + assertThat(alarm.isEmpty()).isFalse(); + + // phase 2 + Optional room = getObjectByType(Room.class); + assertThat(room.isEmpty()).isFalse(); + Sprinkler sprinkler1 = new Sprinkler((Room) room.get()); + insert(sprinkler1); + + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Turn on sprinkler rule"); + clearResults(); + + Optional sprinkler = getObjectByType(Sprinkler.class); + assertThat(sprinkler.isEmpty()).isFalse(); + assertThat(((Sprinkler) sprinkler.get()).isOn()).isTrue(); + + failover(); + restoreSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + clearResults(); + + // phase 3 + Optional fireFh = getFactHandleByType(Fire.class); + assertThat(fireFh.isEmpty()).isFalse(); + delete(fireFh.get()); + assertThat(fireAllRules()).isEqualTo(1); + assertThat(getResults()).containsExactlyInAnyOrder("Cancel alarm rule"); + } + + @ParameterizedTest + @MethodSource("strategyProviderStoresOnlyWithExplicitSafepoints") // does not support PersistedSessionOption.PersistenceObjectsStrategy.SIMPLE + void testInsertFailoverUpdate_shouldFireRules(PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy) { + createSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + + Room room1 = new Room("Room 1"); + insert(room1); + Sprinkler sprinkler1 = new Sprinkler(room1); + insert(sprinkler1); + insert(new Fire(room1)); + + assertThat(fireAllRules()).isEqualTo(2); + assertThat(getResults()).containsExactlyInAnyOrder("Turn on sprinkler rule", "Raise alarm rule"); + + failover(); + restoreSession(FIRE_AND_ALARM, persistenceStrategy, safepointStrategy, PersistedSessionOption.PersistenceObjectsStrategy.OBJECT_REFERENCES); + clearResults(); + + Optional fireFh = this.getFactHandleByType(Fire.class); + delete(fireFh.get()); + + Optional sprinklerFh = this.getFactHandleByType(Sprinkler.class); + Sprinkler sprinkler2 = ((Sprinkler) sprinklerFh.get().getObject()); + sprinkler2.setOn(false); + update(sprinklerFh.get(), sprinkler2); + + assertThat(fireAllRules()).isEqualTo(2); + assertThat(getResults()).containsExactlyInAnyOrder("Everything ok rule", "Cancel alarm rule"); + + } +} diff --git a/drools-reliability/drools-reliability-tests/src/test/java/org/drools/reliability/test/ReliabilityTestBasics.java b/drools-reliability/drools-reliability-tests/src/test/java/org/drools/reliability/test/ReliabilityTestBasics.java index 687b1db80fa..dc42fb7ad50 100644 --- a/drools-reliability/drools-reliability-tests/src/test/java/org/drools/reliability/test/ReliabilityTestBasics.java +++ b/drools-reliability/drools-reliability-tests/src/test/java/org/drools/reliability/test/ReliabilityTestBasics.java @@ -15,14 +15,6 @@ package org.drools.reliability.test; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - import org.drools.base.facttemplates.Event; import org.drools.core.ClassObjectFilter; import org.drools.kiesession.session.StatefulKnowledgeSessionImpl; @@ -57,10 +49,18 @@ import org.slf4j.LoggerFactory; import org.test.domain.Person; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + import static org.drools.reliability.infinispan.InfinispanStorageManagerFactory.INFINISPAN_STORAGE_MARSHALLER; import static org.drools.reliability.infinispan.InfinispanStorageManagerFactory.INFINISPAN_STORAGE_MODE; -import static org.drools.reliability.test.util.TestConfigurationUtils.DROOLS_RELIABILITY_MODULE_TEST; import static org.drools.reliability.test.util.PrototypeUtils.createEvent; +import static org.drools.reliability.test.util.TestConfigurationUtils.DROOLS_RELIABILITY_MODULE_TEST; import static org.drools.util.Config.getConfig; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -256,6 +256,12 @@ protected KieSession createSession(String drl, PersistedSessionOption.Persistenc return getKieSession(drl, persistenceStrategy != null ? PersistedSessionOption.newSession().withPersistenceStrategy(persistenceStrategy).withSafepointStrategy(safepointStrategy) : null, options); } + protected KieSession createSession(String drl, PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy, + PersistedSessionOption.PersistenceObjectsStrategy persistenceObjectsStrategy, Option... options) { + return getKieSession(drl, persistenceStrategy != null ? PersistedSessionOption.newSession().withPersistenceStrategy(persistenceStrategy) + .withSafepointStrategy(safepointStrategy).withPersistenceObjectsStrategy(persistenceObjectsStrategy) : null, options); + } + protected KieSession createSession(String drl, PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy, boolean useKieBaseCache, Option... options) { return getKieSession(drl, persistenceStrategy != null ? PersistedSessionOption.newSession().withPersistenceStrategy(persistenceStrategy).withSafepointStrategy(safepointStrategy) : null, useKieBaseCache, options); } @@ -273,11 +279,24 @@ protected KieSession restoreSession(String drl, PersistedSessionOption.Persisten return restoreSession(sessionIdToRestoreFrom, drl, persistenceStrategy, safepointStrategy, options); } + protected KieSession restoreSession(String drl, PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy, + PersistedSessionOption.PersistenceObjectsStrategy persistenceObjectsStrategy, Option... options) { + Long sessionIdToRestoreFrom = (Long)this.persistedSessionIds.values().toArray()[0]; + return restoreSession(sessionIdToRestoreFrom, drl, persistenceStrategy, safepointStrategy, persistenceObjectsStrategy, options); + } + protected KieSession restoreSession(Long sessionId, String drl, PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy, Option... options) { Long sessionIdToRestoreFrom = this.persistedSessionIds.get(sessionId); return getKieSession(drl, PersistedSessionOption.fromSession(sessionIdToRestoreFrom).withPersistenceStrategy(persistenceStrategy).withSafepointStrategy(safepointStrategy), options); } + protected KieSession restoreSession(Long sessionId, String drl, PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy, + PersistedSessionOption.PersistenceObjectsStrategy persistenceObjectsStrategy, Option... options) { + Long sessionIdToRestoreFrom = this.persistedSessionIds.get(sessionId); + return getKieSession(drl, PersistedSessionOption.fromSession(sessionIdToRestoreFrom).withPersistenceStrategy(persistenceStrategy) + .withSafepointStrategy(safepointStrategy).withPersistenceObjectsStrategy(persistenceObjectsStrategy), options); + } + protected KieSession restoreSession(Long sessionId, String drl, PersistedSessionOption.PersistenceStrategy persistenceStrategy, PersistedSessionOption.SafepointStrategy safepointStrategy, boolean useKieBaseCache, Option... options) { Long sessionIdToRestoreFrom = this.persistedSessionIds.get(sessionId); return getKieSession(drl, PersistedSessionOption.fromSession(sessionIdToRestoreFrom).withPersistenceStrategy(persistenceStrategy).withSafepointStrategy(safepointStrategy), useKieBaseCache, options); @@ -397,6 +416,25 @@ protected Optional getFactHandle(KieSession kieSession, Person perso .filter(p -> ( (Person) p.getObject()).getAge()==person.getAge() ).findFirst(); } + protected Optional getObjectByType(Class objectClass){ + return getObjectByType(sessions.get(0), objectClass); + } + + protected Optional getObjectByType(KieSession kieSession, Class objectClass){ + return (Optional) kieSession.getObjects(new ClassObjectFilter(objectClass)).stream().findFirst(); + } + + protected Optional getFactHandleByType(Class objectClass){ + return getFactHandleByType(sessions.get(0), objectClass); + } + + protected Optional getFactHandleByType(KieSession kieSession, Class objectClass){ + return kieSession.getFactHandles() + .stream() + .filter(fh -> fh.getObject().getClass().equals(objectClass)) + .findFirst(); + } + private static class OptionsFilter { private final Option[] options; diff --git a/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Alarm.java b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Alarm.java new file mode 100644 index 00000000000..70c3496c410 --- /dev/null +++ b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Alarm.java @@ -0,0 +1,21 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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.test.domain.fireandalarm; + +import java.io.Serializable; + +public class Alarm implements Serializable { +} diff --git a/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Fire.java b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Fire.java new file mode 100644 index 00000000000..e4f44929c9a --- /dev/null +++ b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Fire.java @@ -0,0 +1,36 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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.test.domain.fireandalarm; + +import java.io.Serializable; + +public class Fire implements Serializable { + private Room room; + + public Fire() { } + + public Fire(Room room) { + this.room = room; + } + + public Room getRoom() { + return room; + } + + public void setRoom(Room room) { + this.room = room; + } +} diff --git a/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Room.java b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Room.java new file mode 100644 index 00000000000..cbb073779a6 --- /dev/null +++ b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Room.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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.test.domain.fireandalarm; + +import java.io.Serializable; + +public class Room implements Serializable { + + private String name; + + public Room() { } + + public Room(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Sprinkler.java b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Sprinkler.java new file mode 100644 index 00000000000..67a085e2032 --- /dev/null +++ b/drools-reliability/drools-reliability-tests/src/test/java/org/test/domain/fireandalarm/Sprinkler.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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.test.domain.fireandalarm; + +import java.io.Serializable; + +public class Sprinkler implements Serializable { + private Room room; + private boolean on = false; + + public Sprinkler() { } + + public Sprinkler(Room room) { + this.room = room; + } + + public Room getRoom() { + return room; + } + + public void setRoom(Room room) { + this.room = room; + } + + public boolean isOn() { + return on; + } + + public void setOn(boolean on) { + this.on = on; + } + + @Override + public String toString() { + return "Sprinkler for " + room; + } +} diff --git a/kie-api/src/main/java/org/kie/api/runtime/conf/PersistedSessionOption.java b/kie-api/src/main/java/org/kie/api/runtime/conf/PersistedSessionOption.java index aa6fa2082ee..e0bef84f904 100644 --- a/kie-api/src/main/java/org/kie/api/runtime/conf/PersistedSessionOption.java +++ b/kie-api/src/main/java/org/kie/api/runtime/conf/PersistedSessionOption.java @@ -28,6 +28,10 @@ public enum PersistenceStrategy { FULL, STORES_ONLY } + public enum PersistenceObjectsStrategy { + SIMPLE, OBJECT_REFERENCES + } + public enum SafepointStrategy { ALWAYS, AFTER_FIRE, EXPLICIT; @@ -45,6 +49,8 @@ public boolean useSafepoints() { private PersistenceStrategy persistenceStrategy = PersistenceStrategy.FULL; + private PersistenceObjectsStrategy persistenceObjectsStrategy = PersistenceObjectsStrategy.SIMPLE; + private SafepointStrategy safepointStrategy = SafepointStrategy.ALWAYS; private PersistedSessionOption() { @@ -78,6 +84,13 @@ public PersistenceStrategy getPersistenceStrategy() { return persistenceStrategy; } + public PersistenceObjectsStrategy getPersistenceObjectsStrategy() {return persistenceObjectsStrategy;} + + public PersistedSessionOption withPersistenceObjectsStrategy(PersistenceObjectsStrategy persistenceObjectsStrategy){ + this.persistenceObjectsStrategy = persistenceObjectsStrategy; + return this; + } + public PersistedSessionOption withPersistenceStrategy(PersistenceStrategy persistenceStrategy) { this.persistenceStrategy = persistenceStrategy; return this;