From dd580021587667fe3c3eca312c57ff66f10356f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Mon, 1 Sep 2025 09:08:26 +0200 Subject: [PATCH 01/28] NAE-2182 - Implementation of an endpoint for clearing the action cache --- .../FieldActionCacheEndpoint.java | 26 +++++++++++++++++++ .../src/main/resources/application.yaml | 1 + 2 files changed, 27 insertions(+) create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java new file mode 100644 index 0000000000..2edfee2adb --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java @@ -0,0 +1,26 @@ +package com.netgrif.application.engine.configuration; + +import com.netgrif.application.engine.workflow.service.FieldActionsCacheService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@Endpoint(id = "fieldActions") +public class FieldActionCacheEndpoint { + + private final FieldActionsCacheService cacheService; + + public FieldActionCacheEndpoint(FieldActionsCacheService cacheService) { + this.cacheService = cacheService; + } + + @DeleteOperation + public String clear() { + cacheService.clearActionCache(); + log.info("actionsCache cleared"); + return "actionsCache cleared"; + } +} diff --git a/application-engine/src/main/resources/application.yaml b/application-engine/src/main/resources/application.yaml index 4e2bb303c1..93e9681bf5 100644 --- a/application-engine/src/main/resources/application.yaml +++ b/application-engine/src/main/resources/application.yaml @@ -117,6 +117,7 @@ netgrif: prometheus: metric exposure: exclude: shutdown + include: fieldActions endpoint: health: show-details: when_authorized From 5b98ca49984500cd59fe7e37d8beff0309491f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Mon, 1 Sep 2025 15:10:05 +0200 Subject: [PATCH 02/28] NAE-2182 - Implementation of custom cache wrapper to use in cacheManager --- .../configuration/ActionsCacheWrapper.java | 54 +++++++++++++++++++ .../configuration/CacheConfiguration.java | 24 ++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java new file mode 100644 index 0000000000..73e1a8c1c4 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java @@ -0,0 +1,54 @@ +package com.netgrif.application.engine.configuration; + +import com.netgrif.application.engine.workflow.service.FieldActionsCacheService; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.springframework.cache.Cache; + +import java.util.concurrent.Callable; + +@Slf4j +public class ActionsCacheWrapper implements Cache { + private final Cache cache; + private final FieldActionsCacheService fieldActionsCacheService; + + + public ActionsCacheWrapper(Cache cache, FieldActionsCacheService fieldActionsCacheService) { + this.cache = cache; + this.fieldActionsCacheService = fieldActionsCacheService; + } + @NotNull + @Override + public String getName() { + return cache.getName(); + } + @NotNull + @Override + public Object getNativeCache() { + return cache.getNativeCache(); + } + @NotNull + @Override public ValueWrapper get(@NotNull Object key) { + return cache.get(key); + } + @NotNull + @Override public T get(@NotNull Object key, @NotNull Class type) { + return cache.get(key, type); + } + @NotNull + @Override public T get(@NotNull Object key, @NotNull Callable loader) { + return cache.get(key, loader); + } + @Override public void put(@NotNull Object key, @NotNull Object value) { + cache.put(key, value); + } + @Override public void evict(@NotNull Object key) { + cache.evict(key); + } + + @Override public void clear() { + cache.clear(); + log.info("actionsCache cleared from cache actuator"); + fieldActionsCacheService.clearActionCache(); + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 1e0ad25c87..709067d68a 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -1,30 +1,50 @@ package com.netgrif.application.engine.configuration; import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; +import com.netgrif.application.engine.workflow.service.FieldActionsCacheService; +import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.cache.interceptor.CacheResolver; +import org.springframework.cache.support.SimpleCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import java.lang.reflect.Array; +import java.util.*; +import java.util.stream.Collectors; + @Configuration @EnableCaching public class CacheConfiguration extends CachingConfigurerSupport { private final CacheConfigurationProperties properties; + private final FieldActionsCacheService fieldActionsCacheService; - public CacheConfiguration(CacheConfigurationProperties properties) { + public CacheConfiguration(CacheConfigurationProperties properties, FieldActionsCacheService fieldActionsCacheService) { this.properties = properties; + this.fieldActionsCacheService = fieldActionsCacheService; } @Bean @Primary @Override public CacheManager cacheManager() { - return new ConcurrentMapCacheManager(properties.getAllCaches().toArray(String[]::new)); + Set cacheNames = properties.getAllCaches(); + + List caches = cacheNames.stream() + .map(ConcurrentMapCache::new) + .collect(Collectors.toCollection(ArrayList::new)); + + caches.add(new ActionsCacheWrapper(new ConcurrentMapCache("actionsCache"), fieldActionsCacheService)); + + SimpleCacheManager cacheManager = new SimpleCacheManager(); + cacheManager.setCaches(caches); + return cacheManager; } @Bean From e917f763c4e4bab03fb94735136fe2128c3ef85f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Tue, 2 Sep 2025 13:41:13 +0200 Subject: [PATCH 03/28] NAE-2182 - Implementation of generic map cache and created actuator cache endpoints for actionsCache, functionsCache and namespaceFunctionsCache --- .../configuration/ActionsCacheWrapper.java | 54 ------------- .../configuration/CacheConfiguration.java | 40 +++++++--- .../engine/configuration/CacheMapKeys.java | 7 ++ .../FieldActionCacheEndpoint.java | 26 ------- .../engine/configuration/GenericMapCache.java | 78 +++++++++++++++++++ .../service/FieldActionsCacheService.java | 66 ++++++++-------- .../src/main/resources/application.yaml | 2 +- 7 files changed, 152 insertions(+), 121 deletions(-) delete mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java delete mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java deleted file mode 100644 index 73e1a8c1c4..0000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsCacheWrapper.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.netgrif.application.engine.configuration; - -import com.netgrif.application.engine.workflow.service.FieldActionsCacheService; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.cache.Cache; - -import java.util.concurrent.Callable; - -@Slf4j -public class ActionsCacheWrapper implements Cache { - private final Cache cache; - private final FieldActionsCacheService fieldActionsCacheService; - - - public ActionsCacheWrapper(Cache cache, FieldActionsCacheService fieldActionsCacheService) { - this.cache = cache; - this.fieldActionsCacheService = fieldActionsCacheService; - } - @NotNull - @Override - public String getName() { - return cache.getName(); - } - @NotNull - @Override - public Object getNativeCache() { - return cache.getNativeCache(); - } - @NotNull - @Override public ValueWrapper get(@NotNull Object key) { - return cache.get(key); - } - @NotNull - @Override public T get(@NotNull Object key, @NotNull Class type) { - return cache.get(key, type); - } - @NotNull - @Override public T get(@NotNull Object key, @NotNull Callable loader) { - return cache.get(key, loader); - } - @Override public void put(@NotNull Object key, @NotNull Object value) { - cache.put(key, value); - } - @Override public void evict(@NotNull Object key) { - cache.evict(key); - } - - @Override public void clear() { - cache.clear(); - log.info("actionsCache cleared from cache actuator"); - fieldActionsCacheService.clearActionCache(); - } -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 709067d68a..33de904b44 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -1,33 +1,33 @@ package com.netgrif.application.engine.configuration; import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; -import com.netgrif.application.engine.workflow.service.FieldActionsCacheService; +import com.netgrif.application.engine.configuration.properties.RunnerConfigurationProperties; +import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; +import com.netgrif.application.engine.workflow.domain.CachedFunction; +import groovy.lang.Closure; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCache; -import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.cache.interceptor.CacheResolver; import org.springframework.cache.support.SimpleCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import java.lang.reflect.Array; import java.util.*; import java.util.stream.Collectors; @Configuration @EnableCaching public class CacheConfiguration extends CachingConfigurerSupport { - + private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties; private final CacheConfigurationProperties properties; - private final FieldActionsCacheService fieldActionsCacheService; - public CacheConfiguration(CacheConfigurationProperties properties, FieldActionsCacheService fieldActionsCacheService) { + public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties) { + this.fieldRunnerProperties = fieldRunnerProperties; this.properties = properties; - this.fieldActionsCacheService = fieldActionsCacheService; } @Bean @@ -35,12 +35,34 @@ public CacheConfiguration(CacheConfigurationProperties properties, FieldActionsC @Override public CacheManager cacheManager() { Set cacheNames = properties.getAllCaches(); - List caches = cacheNames.stream() .map(ConcurrentMapCache::new) .collect(Collectors.toCollection(ArrayList::new)); - caches.add(new ActionsCacheWrapper(new ConcurrentMapCache("actionsCache"), fieldActionsCacheService)); + + java.util.function.Supplier> actionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize()); + + caches.add(new GenericMapCache<>( + CacheMapKeys.ACTIONS, + Closure.class, + actionsFactory + )); + + java.util.function.Supplier> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize()); + + caches.add(new GenericMapCache<>( + CacheMapKeys.FUNCTIONS, + CachedFunction.class, + functionsFactory + )); + + java.util.function.Supplier>> nsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize()); + + caches.add(new GenericMapCache<>( + CacheMapKeys.NAMESPACE_FUNCTIONS, + List.class, + nsFactory + )); SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(caches); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java new file mode 100644 index 0000000000..8928b0f43b --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java @@ -0,0 +1,7 @@ +package com.netgrif.application.engine.configuration; + +public class CacheMapKeys { + public static final String ACTIONS = "actionsCache"; + public static final String FUNCTIONS = "functionsCache"; + public static final String NAMESPACE_FUNCTIONS = "namespaceFunctionsCache"; +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java deleted file mode 100644 index 2edfee2adb..0000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FieldActionCacheEndpoint.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.netgrif.application.engine.configuration; - -import com.netgrif.application.engine.workflow.service.FieldActionsCacheService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; -import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@Endpoint(id = "fieldActions") -public class FieldActionCacheEndpoint { - - private final FieldActionsCacheService cacheService; - - public FieldActionCacheEndpoint(FieldActionsCacheService cacheService) { - this.cacheService = cacheService; - } - - @DeleteOperation - public String clear() { - cacheService.clearActionCache(); - log.info("actionsCache cleared"); - return "actionsCache cleared"; - } -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java new file mode 100644 index 0000000000..1c53f632fb --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -0,0 +1,78 @@ +package com.netgrif.application.engine.configuration; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.Cache; + +import java.util.Map; + +@Slf4j +public class GenericMapCache implements Cache { + private final String name; + protected Class valueType; + private final java.util.function.Supplier> mapFactory; + private volatile Map map; + + public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory) { + this.name = name; + this.valueType = valueType; + this.mapFactory = mapFactory; + this.map = mapFactory.get(); + } + + @Override public String getName() { return name; } + @Override public Object getNativeCache() { return map; } + + @Override public ValueWrapper get(Object key) { + Object valueObject = map.get(String.valueOf(key)); + return (valueObject == null) ? null : new org.springframework.cache.support.SimpleValueWrapper(valueObject); + } + + @Override public T get(Object key, Class type) { + Object valueObject = map.get(String.valueOf(key)); + return (valueObject == null) ? null : type.cast(valueObject); + } + + @Override public T get(Object key, java.util.concurrent.Callable loader) { + String stringKey = String.valueOf(key); + Object present = map.get(stringKey); + + if (present != null) { + return (T) present; + } + + try { + T computed = loader.call(); + if (computed != null) { + map.put(stringKey, safeCast(computed)); + } + return computed; + } catch (Exception ex) { + throw new org.springframework.cache.Cache.ValueRetrievalException(stringKey, loader, ex); + } + } + + @Override public void put(Object key, Object value) { + map.put(String.valueOf(key), safeCast(value)); + } + + @Override public void evict(Object key) { + map.remove(String.valueOf(key)); + } + + @Override public void clear() { + this.map = mapFactory.get(); + log.info("{} cache cleared", this.getName()); + } + + protected V safeCast(Object object) { + if (object == null) { + return null; + } + + if (!valueType.isInstance(object)) { + throw new ClassCastException("Expected " + valueType.getName() + " but was " + object.getClass().getName()); + } + + return (V) object; + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 1b996bc60d..ddb8d80b30 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -1,7 +1,6 @@ package com.netgrif.application.engine.workflow.service; -import com.netgrif.application.engine.configuration.properties.RunnerConfigurationProperties; -import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; +import com.netgrif.application.engine.configuration.CacheMapKeys; import com.netgrif.application.engine.event.IGroovyShellFactory; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import com.netgrif.application.engine.objects.petrinet.domain.Function; @@ -14,33 +13,26 @@ import groovy.lang.GroovyShell; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Service @Slf4j public class FieldActionsCacheService implements IFieldActionsCacheService { - private final RunnerConfigurationProperties.FieldRunnerProperties properties; + private final CacheManager cacheManager; private IPetriNetService petriNetService; - private Map actionsCache; - private Map> namespaceFunctionsCache; - private Map functionsCache; private final GroovyShell shell; - public FieldActionsCacheService(RunnerConfigurationProperties.FieldRunnerProperties properties, IGroovyShellFactory shellFactory) { - this.properties = properties; - this.actionsCache = new MaxSizeHashMap<>(properties.getActionCacheSize()); - this.functionsCache = new MaxSizeHashMap<>(properties.getFunctionsCacheSize()); - this.namespaceFunctionsCache = new MaxSizeHashMap<>(properties.getNamespaceCacheSize()); + public FieldActionsCacheService(CacheManager cacheManager, IGroovyShellFactory shellFactory) { + this.cacheManager = cacheManager; this.shell = shellFactory.getGroovyShell(); } @@ -60,39 +52,51 @@ public void cachePetriNetFunctions(PetriNet petriNet) { .map(function -> CachedFunction.build(shell, function)) .collect(Collectors.toList()); + Cache namespaceFunctionsCache = cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS); + if (!functions.isEmpty()) { evaluateCachedFunctions(functions); namespaceFunctionsCache.put(petriNet.getIdentifier(), functions); } else { - namespaceFunctionsCache.remove(petriNet.getIdentifier()); + namespaceFunctionsCache.evictIfPresent(petriNet.getIdentifier()); } } @Override public void reloadCachedFunctions(PetriNet petriNet) { - namespaceFunctionsCache.remove(petriNet.getIdentifier()); + cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNet.getIdentifier()); cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNet.getIdentifier())); } @Override public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) { String stringId = action.getId().toString(); - if (shouldRewriteCachedActions || !actionsCache.containsKey(stringId)) { - Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); - actionsCache.put(stringId, code); + Cache actionsCache = cacheManager.getCache(CacheMapKeys.ACTIONS); + Object nativeActionsCache = actionsCache.getNativeCache(); + + if (nativeActionsCache instanceof Map map) { + if (shouldRewriteCachedActions || map.containsKey(stringId) ) { + Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); + actionsCache.put(stringId, code); + } } - return actionsCache.get(stringId); + return (Closure) actionsCache.get(stringId).get(); } @Override public List getCachedFunctions(List functions) { List cachedFunctions = new ArrayList<>(functions.size()); - functions.forEach(function -> { - if (!functionsCache.containsKey(function.getStringId())) { - functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); - } - cachedFunctions.add(functionsCache.get(function.getStringId())); - }); + Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS); + Object nativeFunctionsCache = functionsCache.getNativeCache(); + + if (nativeFunctionsCache instanceof Map map) { + functions.forEach(function -> { + if (!map.containsKey(function.getStringId())) { + functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); + } + cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); + }); + } return cachedFunctions; } @@ -135,21 +139,21 @@ private String stringifyParameterTypes(Class[] a) { @Override public Map> getNamespaceFunctionCache() { - return new HashMap<>(namespaceFunctionsCache); + return new HashMap<>((Map) cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).getNativeCache()); } @Override public void clearActionCache() { - this.actionsCache = new MaxSizeHashMap<>(properties.getActionCacheSize()); + cacheManager.getCache(CacheMapKeys.ACTIONS).clear(); } @Override public void clearNamespaceFunctionCache() { - this.namespaceFunctionsCache = new MaxSizeHashMap<>(properties.getNamespaceCacheSize()); + cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).clear(); } @Override public void clearFunctionCache() { - this.functionsCache = new MaxSizeHashMap<>(properties.getFunctionsCacheSize()); + cacheManager.getCache(CacheMapKeys.FUNCTIONS).clear(); } } diff --git a/application-engine/src/main/resources/application.yaml b/application-engine/src/main/resources/application.yaml index 93e9681bf5..da22653c8a 100644 --- a/application-engine/src/main/resources/application.yaml +++ b/application-engine/src/main/resources/application.yaml @@ -117,7 +117,7 @@ netgrif: prometheus: metric exposure: exclude: shutdown - include: fieldActions + include: "*" endpoint: health: show-details: when_authorized From bd719b580c37af76fbe600efb110fe8562e03801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Wed, 3 Sep 2025 20:08:20 +0200 Subject: [PATCH 04/28] NAE-2182 - cache FunctionsNamespace and functions if not found --- .../engine/configuration/ActionsMapCache.java | 41 +++++++++++++++ .../configuration/CacheConfiguration.java | 38 +++++++++----- .../configuration/FunctionsMapCache.java | 52 +++++++++++++++++++ .../FunctionsNamespaceMapCache.java | 45 ++++++++++++++++ .../engine/configuration/GenericMapCache.java | 36 +++++++------ .../petrinet/service/PetriNetService.java | 19 +++++-- .../service/interfaces/IPetriNetService.java | 16 ++++-- .../service/FieldActionsCacheService.java | 13 +++-- .../interfaces/IFieldActionsCacheService.java | 2 + 9 files changed, 220 insertions(+), 42 deletions(-) create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java create mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java new file mode 100644 index 0000000000..c2701c6a43 --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java @@ -0,0 +1,41 @@ +package com.netgrif.application.engine.configuration; + +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; +import groovy.lang.Closure; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +@Slf4j +public class ActionsMapCache extends GenericMapCache { + + public ActionsMapCache(String name, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { + super(name, Closure.class, mapFactory, fieldActionsCacheService, petriNetService); + } + + @Override + public ValueWrapper get(Object key) { + String stringKey = String.valueOf(key); + + Object valueObject = map.get(stringKey); + if (valueObject != null) { + return new org.springframework.cache.support.SimpleValueWrapper(valueObject); + } + fieldActionsCacheService.reloadCachedFunctions(stringKey); + return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey)); + } + + public T get(Object key, Class type) { + String stringKey = String.valueOf(key); + Object valueObject = map.get(stringKey); + + if (valueObject != null) { + return type.cast(valueObject); + } + + fieldActionsCacheService.reloadCachedFunctions(stringKey); + return type.cast(map.get(stringKey)); + + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 33de904b44..db26422602 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -3,7 +3,9 @@ import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.configuration.properties.RunnerConfigurationProperties; import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.workflow.domain.CachedFunction; +import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import groovy.lang.Closure; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; @@ -17,6 +19,7 @@ import org.springframework.context.annotation.Primary; import java.util.*; +import java.util.function.Supplier; import java.util.stream.Collectors; @Configuration @@ -24,10 +27,16 @@ public class CacheConfiguration extends CachingConfigurerSupport { private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties; private final CacheConfigurationProperties properties; + private final IFieldActionsCacheService fieldActionsCacheService; - public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties) { + private final IPetriNetService petriNetService; + + + public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { this.fieldRunnerProperties = fieldRunnerProperties; this.properties = properties; + this.fieldActionsCacheService = fieldActionsCacheService; + this.petriNetService = petriNetService; } @Bean @@ -40,28 +49,31 @@ public CacheManager cacheManager() { .collect(Collectors.toCollection(ArrayList::new)); - java.util.function.Supplier> actionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize()); + Supplier> actionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize()); - caches.add(new GenericMapCache<>( + caches.add(new ActionsMapCache( CacheMapKeys.ACTIONS, - Closure.class, - actionsFactory + actionsFactory, + fieldActionsCacheService, + petriNetService )); - java.util.function.Supplier> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize()); + Supplier> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize()); - caches.add(new GenericMapCache<>( + caches.add(new FunctionsMapCache( CacheMapKeys.FUNCTIONS, - CachedFunction.class, - functionsFactory + functionsFactory, + fieldActionsCacheService, + petriNetService )); - java.util.function.Supplier>> nsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize()); + Supplier>> nsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize()); - caches.add(new GenericMapCache<>( + caches.add(new FunctionsNamespaceMapCache( CacheMapKeys.NAMESPACE_FUNCTIONS, - List.class, - nsFactory + nsFactory, + fieldActionsCacheService, + petriNetService )); SimpleCacheManager cacheManager = new SimpleCacheManager(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java new file mode 100644 index 0000000000..70b9cdc1cb --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java @@ -0,0 +1,52 @@ +package com.netgrif.application.engine.configuration; + +import com.netgrif.application.engine.objects.petrinet.domain.Function; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.workflow.domain.CachedFunction; +import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +@Slf4j +public class FunctionsMapCache extends GenericMapCache { + + public FunctionsMapCache(String name, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { + super(name, CachedFunction.class, mapFactory, fieldActionsCacheService, petriNetService); + } + + @Override + public ValueWrapper get(Object key) { + String stringKey = String.valueOf(key); + + Object valueObject = map.get(stringKey); + if (valueObject != null) { + return new org.springframework.cache.support.SimpleValueWrapper(valueObject); + } + + Function function = petriNetService.findByFunctionId(stringKey); + if (function != null) { + map.put(stringKey, function); + return new org.springframework.cache.support.SimpleValueWrapper(function); + } else { + return new org.springframework.cache.support.SimpleValueWrapper(null); + } + } + + public T get(Object key, Class type) { + String stringKey = String.valueOf(key); + Object valueObject = map.get(stringKey); + + if (valueObject != null) { + return type.cast(valueObject); + } + + Function function = petriNetService.findByFunctionId(stringKey); + if (function != null) { + map.put(stringKey, function); + return type.cast(function); + } else { + return type.cast(null); + } + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java new file mode 100644 index 0000000000..66850c3a7a --- /dev/null +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java @@ -0,0 +1,45 @@ +package com.netgrif.application.engine.configuration; + +import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.workflow.domain.CachedFunction; +import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; + +@Slf4j +public class FunctionsNamespaceMapCache extends GenericMapCache { + + public FunctionsNamespaceMapCache(String name, java.util.function.Supplier>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { + super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService); + } + + @Override + public ValueWrapper get(Object key) { + String stringKey = String.valueOf(key); + + Object valueObject = map.get(stringKey); + if (valueObject != null) { + return new org.springframework.cache.support.SimpleValueWrapper(valueObject); + } + PetriNet petriNet = petriNetService.getPetriNet(stringKey); + fieldActionsCacheService.cachePetriNetFunctions(petriNet); + return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey)); + } + + public T get(Object key, Class type) { + String stringKey = String.valueOf(key); + Object valueObject = map.get(stringKey); + + if (valueObject != null) { + return type.cast(valueObject); + } + + PetriNet petriNet = petriNetService.getPetriNet(stringKey); + fieldActionsCacheService.cachePetriNetFunctions(petriNet); + return type.cast(map.get(stringKey)); + + } +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 1c53f632fb..b6e729ec69 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -1,38 +1,37 @@ package com.netgrif.application.engine.configuration; +import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; +import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; import java.util.Map; @Slf4j -public class GenericMapCache implements Cache { +public abstract class GenericMapCache implements Cache { private final String name; protected Class valueType; private final java.util.function.Supplier> mapFactory; - private volatile Map map; + protected volatile Map map; + protected final IFieldActionsCacheService fieldActionsCacheService; - public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory) { + protected final IPetriNetService petriNetService; + + public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { this.name = name; this.valueType = valueType; this.mapFactory = mapFactory; this.map = mapFactory.get(); + this.fieldActionsCacheService = fieldActionsCacheService; + this.petriNetService = petriNetService; } @Override public String getName() { return name; } - @Override public Object getNativeCache() { return map; } - - @Override public ValueWrapper get(Object key) { - Object valueObject = map.get(String.valueOf(key)); - return (valueObject == null) ? null : new org.springframework.cache.support.SimpleValueWrapper(valueObject); - } - @Override public T get(Object key, Class type) { - Object valueObject = map.get(String.valueOf(key)); - return (valueObject == null) ? null : type.cast(valueObject); - } + @Override public Object getNativeCache() { return map; } - @Override public T get(Object key, java.util.concurrent.Callable loader) { + @Override + public T get(Object key, java.util.concurrent.Callable loader) { String stringKey = String.valueOf(key); Object present = map.get(stringKey); @@ -51,15 +50,18 @@ public GenericMapCache(String name, Class valueType, java.util.function.Suppl } } - @Override public void put(Object key, Object value) { + @Override + public void put(Object key, Object value) { map.put(String.valueOf(key), safeCast(value)); } - @Override public void evict(Object key) { + @Override + public void evict(Object key) { map.remove(String.valueOf(key)); } - @Override public void clear() { + @Override + public void clear() { this.map = mapFactory.get(); log.info("{} cache cleared", this.getName()); } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index f9dad57a49..0aa54baa2b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -5,6 +5,7 @@ import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.files.minio.StorageConfigurationProperties; +import com.netgrif.application.engine.objects.petrinet.domain.*; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.petrinet.web.responsebodies.ArcImportReference; import com.netgrif.application.engine.objects.auth.domain.Group; @@ -17,10 +18,6 @@ import com.netgrif.application.engine.objects.event.events.petrinet.ProcessDeployEvent; import com.netgrif.application.engine.importer.service.Importer; import com.netgrif.application.engine.auth.service.GroupService; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch; -import com.netgrif.application.engine.objects.petrinet.domain.Transition; -import com.netgrif.application.engine.objects.petrinet.domain.VersionType; import com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action; import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.FieldActionsRunner; import com.netgrif.application.engine.objects.petrinet.domain.events.EventPhase; @@ -558,6 +555,19 @@ public void runActions(List actions, PetriNet petriNet) { }); } + @Override + public Function findByFunctionId(String functionId) { + Query query = new Query(); + + query.addCriteria(Criteria.where("functions._id").is(new ObjectId(functionId))); + + PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet"); + + Optional optionalFunction = petriNet.getFunctions().stream().filter(function -> function.getObjectId().toString().equals(functionId)).findFirst(); + + return optionalFunction.orElse(null); + } + protected T requireNonNull(T obj, Object... item) { if (obj == null) { if (item.length > 0) { @@ -567,4 +577,5 @@ protected T requireNonNull(T obj, Object... item) { } return obj; } + } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index 73921f1e2f..d3d175fdb7 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -1,10 +1,7 @@ package com.netgrif.application.engine.petrinet.service.interfaces; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch; -import com.netgrif.application.engine.objects.petrinet.domain.Transition; -import com.netgrif.application.engine.objects.petrinet.domain.VersionType; +import com.netgrif.application.engine.objects.petrinet.domain.*; import com.netgrif.application.engine.objects.petrinet.domain.dataset.Field; import com.netgrif.application.engine.objects.petrinet.domain.dataset.logic.action.Action; import com.netgrif.application.engine.objects.petrinet.domain.throwable.MissingIconKeyException; @@ -335,4 +332,15 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti * @return a {@link PetriNetImportReference} linking the PetriNet */ PetriNetImportReference getNetFromCase(String caseId); + + + /** + * Finds and returns a single function subdocument from the {@code petriNet} collection + * by its nested {@code functions._id}. + * + * @param functionId the string form of the function's ObjectId (24-hex) + * @return the matching {@code Function} subdocument, or {@code null} if not found + * @throws IllegalArgumentException if {@code functionId} is not a valid ObjectId + */ + Function findByFunctionId(String functionId); } \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index ddb8d80b30..55503ba87b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -62,10 +62,15 @@ public void cachePetriNetFunctions(PetriNet petriNet) { } } + @Override + public void reloadCachedFunctions(String petriNetId) { + cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNetId); + cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId)); + } + @Override public void reloadCachedFunctions(PetriNet petriNet) { - cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNet.getIdentifier()); - cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNet.getIdentifier())); + this.reloadCachedFunctions(petriNet.getIdentifier()); } @Override @@ -75,7 +80,7 @@ public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActio Object nativeActionsCache = actionsCache.getNativeCache(); if (nativeActionsCache instanceof Map map) { - if (shouldRewriteCachedActions || map.containsKey(stringId) ) { + if (shouldRewriteCachedActions || !map.containsKey(stringId) ) { Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); actionsCache.put(stringId, code); } @@ -84,7 +89,7 @@ public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActio } @Override - public List getCachedFunctions(List functions) { + public List getCachedFunctions(List functions) { List cachedFunctions = new ArrayList<>(functions.size()); Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS); Object nativeFunctionsCache = functionsCache.getNativeCache(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java index 6eb2580949..7303a97907 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java @@ -13,6 +13,8 @@ public interface IFieldActionsCacheService { void cachePetriNetFunctions(PetriNet petriNet); + void reloadCachedFunctions(String petriNetId); + void reloadCachedFunctions(PetriNet petriNet); Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions); From e976081e79c6e32b2565cafb589c400b1aade986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Thu, 4 Sep 2025 12:41:24 +0200 Subject: [PATCH 05/28] NAE-2182 - prevent multi thread unexpected behaviour and prevent cache mutation on get or put --- .../engine/configuration/ActionsMapCache.java | 15 ++--- .../configuration/CacheConfiguration.java | 19 +++--- .../configuration/FunctionsMapCache.java | 34 +++++----- .../FunctionsNamespaceMapCache.java | 20 ++++-- .../engine/configuration/GenericMapCache.java | 62 +++++++++++++------ .../workflow/domain/CachedFunction.java | 16 +++++ 6 files changed, 113 insertions(+), 53 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java index c2701c6a43..a08a03b0d7 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java @@ -4,6 +4,7 @@ import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import groovy.lang.Closure; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.support.SimpleValueWrapper; import java.util.Map; @@ -15,27 +16,27 @@ public ActionsMapCache(String name, java.util.function.Supplier T get(Object key, Class type) { + public synchronized T get(Object key, Class type) { String stringKey = String.valueOf(key); - Object valueObject = map.get(stringKey); + Object valueObject = map().get(stringKey); if (valueObject != null) { return type.cast(valueObject); } fieldActionsCacheService.reloadCachedFunctions(stringKey); - return type.cast(map.get(stringKey)); + return type.cast(map().get(stringKey)); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index db26422602..4e0245245b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -3,6 +3,7 @@ import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.configuration.properties.RunnerConfigurationProperties; import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; +import com.netgrif.application.engine.event.IGroovyShellFactory; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.workflow.domain.CachedFunction; import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; @@ -28,15 +29,15 @@ public class CacheConfiguration extends CachingConfigurerSupport { private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties; private final CacheConfigurationProperties properties; private final IFieldActionsCacheService fieldActionsCacheService; - private final IPetriNetService petriNetService; + private final IGroovyShellFactory groovyShellFactory; - - public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { + public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService, IGroovyShellFactory shellFactory) { this.fieldRunnerProperties = fieldRunnerProperties; this.properties = properties; this.fieldActionsCacheService = fieldActionsCacheService; this.petriNetService = petriNetService; + this.groovyShellFactory = shellFactory; } @Bean @@ -49,7 +50,8 @@ public CacheManager cacheManager() { .collect(Collectors.toCollection(ArrayList::new)); - Supplier> actionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize()); + Supplier> actionsFactory = + () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize())); caches.add(new ActionsMapCache( CacheMapKeys.ACTIONS, @@ -58,16 +60,19 @@ public CacheManager cacheManager() { petriNetService )); - Supplier> functionsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize()); + Supplier> functionsFactory = + () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize())); caches.add(new FunctionsMapCache( CacheMapKeys.FUNCTIONS, functionsFactory, fieldActionsCacheService, - petriNetService + petriNetService, + groovyShellFactory )); - Supplier>> nsFactory = () -> new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize()); + Supplier>> nsFactory = + () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize())); caches.add(new FunctionsNamespaceMapCache( CacheMapKeys.NAMESPACE_FUNCTIONS, diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java index 70b9cdc1cb..38b0576079 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java @@ -1,41 +1,47 @@ package com.netgrif.application.engine.configuration; +import com.netgrif.application.engine.event.IGroovyShellFactory; import com.netgrif.application.engine.objects.petrinet.domain.Function; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.workflow.domain.CachedFunction; import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; +import groovy.lang.GroovyShell; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.support.SimpleValueWrapper; import java.util.Map; @Slf4j public class FunctionsMapCache extends GenericMapCache { - public FunctionsMapCache(String name, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { + private final GroovyShell shell; + + public FunctionsMapCache(String name, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService, IGroovyShellFactory shellFactory) { super(name, CachedFunction.class, mapFactory, fieldActionsCacheService, petriNetService); + this.shell = shellFactory.getGroovyShell(); } @Override - public ValueWrapper get(Object key) { + public synchronized ValueWrapper get(Object key) { String stringKey = String.valueOf(key); - Object valueObject = map.get(stringKey); + Object valueObject = map().get(stringKey); if (valueObject != null) { - return new org.springframework.cache.support.SimpleValueWrapper(valueObject); + return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject)); } Function function = petriNetService.findByFunctionId(stringKey); if (function != null) { - map.put(stringKey, function); - return new org.springframework.cache.support.SimpleValueWrapper(function); - } else { - return new org.springframework.cache.support.SimpleValueWrapper(null); + map().put(stringKey, CachedFunction.build(shell, function)); + return new SimpleValueWrapper(CachedFunction.build(shell, function)); } + + return new SimpleValueWrapper(null); } - public T get(Object key, Class type) { + public synchronized T get(Object key, Class type) { String stringKey = String.valueOf(key); - Object valueObject = map.get(stringKey); + Object valueObject = map().get(stringKey); if (valueObject != null) { return type.cast(valueObject); @@ -43,10 +49,10 @@ public T get(Object key, Class type) { Function function = petriNetService.findByFunctionId(stringKey); if (function != null) { - map.put(stringKey, function); - return type.cast(function); - } else { - return type.cast(null); + map().put(stringKey, CachedFunction.build(shell, function)); + return type.cast(CachedFunction.build(shell, function)); } + + return type.cast(null); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java index 66850c3a7a..467b80eb3a 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java @@ -1,10 +1,12 @@ package com.netgrif.application.engine.configuration; +import com.netgrif.application.engine.objects.petrinet.domain.Function; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.workflow.domain.CachedFunction; import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.support.SimpleValueWrapper; import java.util.List; import java.util.Map; @@ -20,26 +22,32 @@ public FunctionsNamespaceMapCache(String name, java.util.function.Supplier) valueObject)); } PetriNet petriNet = petriNetService.getPetriNet(stringKey); fieldActionsCacheService.cachePetriNetFunctions(petriNet); - return new org.springframework.cache.support.SimpleValueWrapper(map.get(stringKey)); + return new SimpleValueWrapper(List.copyOf((List) map().get(stringKey))); } public T get(Object key, Class type) { String stringKey = String.valueOf(key); - Object valueObject = map.get(stringKey); + Object valueObject = map().get(stringKey); if (valueObject != null) { - return type.cast(valueObject); + return type.cast(List.copyOf((List) valueObject)); } PetriNet petriNet = petriNetService.getPetriNet(stringKey); fieldActionsCacheService.cachePetriNetFunctions(petriNet); - return type.cast(map.get(stringKey)); + return type.cast(List.copyOf((List) map().get(stringKey))); } + + @Override + public void put(Object key, Object value) { + String k = String.valueOf(key); + map().put(k, List.copyOf((List) value)); + } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index b6e729ec69..6a869653c5 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -4,15 +4,21 @@ import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; +import org.springframework.security.core.parameters.P; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; @Slf4j public abstract class GenericMapCache implements Cache { private final String name; protected Class valueType; private final java.util.function.Supplier> mapFactory; - protected volatile Map map; + private final AtomicReference> atomicMapRef; + + private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); protected final IFieldActionsCacheService fieldActionsCacheService; protected final IPetriNetService petriNetService; @@ -21,51 +27,69 @@ public GenericMapCache(String name, Class valueType, java.util.function.Suppl this.name = name; this.valueType = valueType; this.mapFactory = mapFactory; - this.map = mapFactory.get(); + this.atomicMapRef = new AtomicReference<>(mapFactory.get()); this.fieldActionsCacheService = fieldActionsCacheService; this.petriNetService = petriNetService; } @Override public String getName() { return name; } - @Override public Object getNativeCache() { return map; } + @Override public Object getNativeCache() { return Map.copyOf(map()); } @Override - public T get(Object key, java.util.concurrent.Callable loader) { - String stringKey = String.valueOf(key); - Object present = map.get(stringKey); + @SuppressWarnings("unchecked") + public T get(Object key, Callable loader) { + final String stringKey = String.valueOf(key); - if (present != null) { - return (T) present; + Object mapValue = map().get(stringKey); + if (mapValue != null) { + return (T) mapValue; } + Object lock = locks.computeIfAbsent(stringKey, lockKey -> new Object()); try { - T computed = loader.call(); - if (computed != null) { - map.put(stringKey, safeCast(computed)); + synchronized (lock) { + Object mapLockValue = map().get(stringKey); + if (mapLockValue != null) { + return (T) mapLockValue; + } + + T computed = loader.call(); + if (computed == null) { + return null; + } + + V value = safeCast(computed); + map().put(stringKey, value); + return (T) value; } - return computed; } catch (Exception ex) { - throw new org.springframework.cache.Cache.ValueRetrievalException(stringKey, loader, ex); + throw new ValueRetrievalException(stringKey, loader, ex); + } finally { + locks.remove(stringKey, lock); } } @Override - public void put(Object key, Object value) { - map.put(String.valueOf(key), safeCast(value)); + public synchronized void put(Object key, Object value) { + map().put(String.valueOf(key), safeCast(value)); } @Override - public void evict(Object key) { - map.remove(String.valueOf(key)); + public synchronized void evict(Object key) { + map().remove(String.valueOf(key)); } @Override - public void clear() { - this.map = mapFactory.get(); + public synchronized void clear() { + this.atomicMapRef.set(mapFactory.get()); log.info("{} cache cleared", this.getName()); } + protected Map map() { + return atomicMapRef.get(); + } + protected V safeCast(Object object) { if (object == null) { return null; diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java index 55a6b3f890..139b7c8933 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java @@ -23,4 +23,20 @@ public static CachedFunction build(GroovyShell shell, Function function) { .function(function) .build(); } + + public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) { + if (cachedFunction == null) { + return null; + } + + Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition()); + if (code == null) { + throw new IllegalArgumentException("Non compilable function"); + } + + return CachedFunction.builder() + .code(code) + .function(new Function(cachedFunction.getFunction())) + .build(); + } } From 8323adb34776bdd661269f18957ff6bff31e1d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Tue, 16 Sep 2025 11:00:50 +0200 Subject: [PATCH 06/28] NAE-2182 - Implementation of generic map cache and changed store for namespaceFunctionsCache from local Map to Redis --- .../engine/configuration/ActionsMapCache.java | 42 -------------- .../configuration/CacheConfiguration.java | 36 +++--------- .../engine/configuration/CacheMapKeys.java | 5 +- .../configuration/FunctionsMapCache.java | 58 ------------------- .../FunctionsNamespaceMapCache.java | 53 ----------------- .../engine/configuration/GenericMapCache.java | 38 ++++++++---- .../CacheConfigurationProperties.java | 7 ++- .../service/FieldActionsCacheService.java | 13 +++-- 8 files changed, 51 insertions(+), 201 deletions(-) delete mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java delete mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java delete mode 100644 application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java deleted file mode 100644 index a08a03b0d7..0000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/ActionsMapCache.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.netgrif.application.engine.configuration; - -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; -import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; -import groovy.lang.Closure; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.support.SimpleValueWrapper; - -import java.util.Map; - -@Slf4j -public class ActionsMapCache extends GenericMapCache { - - public ActionsMapCache(String name, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { - super(name, Closure.class, mapFactory, fieldActionsCacheService, petriNetService); - } - - @Override - public synchronized ValueWrapper get(Object key) { - String stringKey = String.valueOf(key); - - Object valueObject = map().get(stringKey); - if (valueObject != null) { - return new SimpleValueWrapper(valueObject); - } - fieldActionsCacheService.reloadCachedFunctions(stringKey); - return new SimpleValueWrapper(map().get(stringKey)); - } - - public synchronized T get(Object key, Class type) { - String stringKey = String.valueOf(key); - Object valueObject = map().get(stringKey); - - if (valueObject != null) { - return type.cast(valueObject); - } - - fieldActionsCacheService.reloadCachedFunctions(stringKey); - return type.cast(map().get(stringKey)); - - } -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 4e0245245b..4d8849fcfc 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -3,10 +3,7 @@ import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.configuration.properties.RunnerConfigurationProperties; import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; -import com.netgrif.application.engine.event.IGroovyShellFactory; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.workflow.domain.CachedFunction; -import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import groovy.lang.Closure; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; @@ -28,16 +25,10 @@ public class CacheConfiguration extends CachingConfigurerSupport { private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties; private final CacheConfigurationProperties properties; - private final IFieldActionsCacheService fieldActionsCacheService; - private final IPetriNetService petriNetService; - private final IGroovyShellFactory groovyShellFactory; - public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService, IGroovyShellFactory shellFactory) { + public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties) { this.fieldRunnerProperties = fieldRunnerProperties; this.properties = properties; - this.fieldActionsCacheService = fieldActionsCacheService; - this.petriNetService = petriNetService; - this.groovyShellFactory = shellFactory; } @Bean @@ -53,32 +44,19 @@ public CacheManager cacheManager() { Supplier> actionsFactory = () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize())); - caches.add(new ActionsMapCache( + caches.add(new GenericMapCache<>( CacheMapKeys.ACTIONS, - actionsFactory, - fieldActionsCacheService, - petriNetService + Closure.class, + actionsFactory )); Supplier> functionsFactory = () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize())); - caches.add(new FunctionsMapCache( + caches.add(new GenericMapCache<>( CacheMapKeys.FUNCTIONS, - functionsFactory, - fieldActionsCacheService, - petriNetService, - groovyShellFactory - )); - - Supplier>> nsFactory = - () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getNamespaceCacheSize())); - - caches.add(new FunctionsNamespaceMapCache( - CacheMapKeys.NAMESPACE_FUNCTIONS, - nsFactory, - fieldActionsCacheService, - petriNetService + CachedFunction.class, + functionsFactory )); SimpleCacheManager cacheManager = new SimpleCacheManager(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java index 8928b0f43b..c99f7a3985 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java @@ -1,7 +1,6 @@ package com.netgrif.application.engine.configuration; public class CacheMapKeys { - public static final String ACTIONS = "actionsCache"; - public static final String FUNCTIONS = "functionsCache"; - public static final String NAMESPACE_FUNCTIONS = "namespaceFunctionsCache"; + public static final String ACTIONS = "actions"; + public static final String FUNCTIONS = "functions"; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java deleted file mode 100644 index 38b0576079..0000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsMapCache.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.netgrif.application.engine.configuration; - -import com.netgrif.application.engine.event.IGroovyShellFactory; -import com.netgrif.application.engine.objects.petrinet.domain.Function; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; -import com.netgrif.application.engine.workflow.domain.CachedFunction; -import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; -import groovy.lang.GroovyShell; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.support.SimpleValueWrapper; - -import java.util.Map; - -@Slf4j -public class FunctionsMapCache extends GenericMapCache { - - private final GroovyShell shell; - - public FunctionsMapCache(String name, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService, IGroovyShellFactory shellFactory) { - super(name, CachedFunction.class, mapFactory, fieldActionsCacheService, petriNetService); - this.shell = shellFactory.getGroovyShell(); - } - - @Override - public synchronized ValueWrapper get(Object key) { - String stringKey = String.valueOf(key); - - Object valueObject = map().get(stringKey); - if (valueObject != null) { - return new SimpleValueWrapper(CachedFunction.copyOf(shell, (CachedFunction) valueObject)); - } - - Function function = petriNetService.findByFunctionId(stringKey); - if (function != null) { - map().put(stringKey, CachedFunction.build(shell, function)); - return new SimpleValueWrapper(CachedFunction.build(shell, function)); - } - - return new SimpleValueWrapper(null); - } - - public synchronized T get(Object key, Class type) { - String stringKey = String.valueOf(key); - Object valueObject = map().get(stringKey); - - if (valueObject != null) { - return type.cast(valueObject); - } - - Function function = petriNetService.findByFunctionId(stringKey); - if (function != null) { - map().put(stringKey, CachedFunction.build(shell, function)); - return type.cast(CachedFunction.build(shell, function)); - } - - return type.cast(null); - } -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java deleted file mode 100644 index 467b80eb3a..0000000000 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/FunctionsNamespaceMapCache.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.netgrif.application.engine.configuration; - -import com.netgrif.application.engine.objects.petrinet.domain.Function; -import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; -import com.netgrif.application.engine.workflow.domain.CachedFunction; -import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.support.SimpleValueWrapper; - -import java.util.List; -import java.util.Map; - -@Slf4j -public class FunctionsNamespaceMapCache extends GenericMapCache { - - public FunctionsNamespaceMapCache(String name, java.util.function.Supplier>> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { - super(name, List.class, mapFactory, fieldActionsCacheService, petriNetService); - } - - @Override - public ValueWrapper get(Object key) { - String stringKey = String.valueOf(key); - - Object valueObject = map().get(stringKey); - if (valueObject != null) { - return new SimpleValueWrapper(List.copyOf((List) valueObject)); - } - PetriNet petriNet = petriNetService.getPetriNet(stringKey); - fieldActionsCacheService.cachePetriNetFunctions(petriNet); - return new SimpleValueWrapper(List.copyOf((List) map().get(stringKey))); - } - - public T get(Object key, Class type) { - String stringKey = String.valueOf(key); - Object valueObject = map().get(stringKey); - - if (valueObject != null) { - return type.cast(List.copyOf((List) valueObject)); - } - - PetriNet petriNet = petriNetService.getPetriNet(stringKey); - fieldActionsCacheService.cachePetriNetFunctions(petriNet); - return type.cast(List.copyOf((List) map().get(stringKey))); - - } - - @Override - public void put(Object key, Object value) { - String k = String.valueOf(key); - map().put(k, List.copyOf((List) value)); - } -} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 6a869653c5..44ac966014 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -1,10 +1,8 @@ package com.netgrif.application.engine.configuration; -import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; -import com.netgrif.application.engine.workflow.service.interfaces.IFieldActionsCacheService; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; -import org.springframework.security.core.parameters.P; +import org.springframework.cache.support.SimpleValueWrapper; import java.util.Map; import java.util.concurrent.Callable; @@ -12,24 +10,19 @@ import java.util.concurrent.atomic.AtomicReference; @Slf4j -public abstract class GenericMapCache implements Cache { +public class GenericMapCache implements Cache { private final String name; protected Class valueType; private final java.util.function.Supplier> mapFactory; private final AtomicReference> atomicMapRef; private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); - protected final IFieldActionsCacheService fieldActionsCacheService; - protected final IPetriNetService petriNetService; - - public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory, IFieldActionsCacheService fieldActionsCacheService, IPetriNetService petriNetService) { + public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory) { this.name = name; this.valueType = valueType; this.mapFactory = mapFactory; this.atomicMapRef = new AtomicReference<>(mapFactory.get()); - this.fieldActionsCacheService = fieldActionsCacheService; - this.petriNetService = petriNetService; } @Override public String getName() { return name; } @@ -70,6 +63,31 @@ public T get(Object key, Callable loader) { } } + @Override + public synchronized ValueWrapper get(Object key) { + String stringKey = String.valueOf(key); + + Object valueObject = map().get(stringKey); + if (valueObject != null) { + return new SimpleValueWrapper(valueObject); + } + + return new SimpleValueWrapper(null); + } + + @Override + public synchronized T get(Object key, Class type) { + String stringKey = String.valueOf(key); + Object valueObject = map().get(stringKey); + + if (valueObject != null) { + return type.cast(valueObject); + } + + return type.cast(null); + + } + @Override public synchronized void put(Object key, Object value) { map().put(String.valueOf(key), safeCast(value)); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java index 8050c7ac37..755829b770 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java @@ -41,6 +41,11 @@ public class CacheConfigurationProperties { */ private String loadedModules = "loadedModules"; + /** + * Default cache name for caching namespace functions. + */ + private String namespaceFunctions = "namespaceFunctions"; + /** * A list of additional custom cache names. * Allows users to define their own cache names for specific use cases. @@ -54,7 +59,7 @@ public class CacheConfigurationProperties { * @return a {@link Set} of all cache names. */ public Set getAllCaches() { - Set caches = new LinkedHashSet<>(Arrays.asList(petriNetById, petriNetByIdentifier, petriNetNewest, petriNetCache, loadedModules)); + Set caches = new LinkedHashSet<>(Arrays.asList(petriNetById, petriNetByIdentifier, petriNetNewest, petriNetCache, loadedModules, namespaceFunctions)); caches.addAll(additional); return caches; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 55503ba87b..b0f7317bb5 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.workflow.service; import com.netgrif.application.engine.configuration.CacheMapKeys; +import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.event.IGroovyShellFactory; import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; import com.netgrif.application.engine.objects.petrinet.domain.Function; @@ -25,13 +26,15 @@ @Slf4j public class FieldActionsCacheService implements IFieldActionsCacheService { + private final CacheConfigurationProperties properties; private final CacheManager cacheManager; private IPetriNetService petriNetService; private final GroovyShell shell; - public FieldActionsCacheService(CacheManager cacheManager, IGroovyShellFactory shellFactory) { + public FieldActionsCacheService(CacheConfigurationProperties properties, CacheManager cacheManager, IGroovyShellFactory shellFactory) { + this.properties = properties; this.cacheManager = cacheManager; this.shell = shellFactory.getGroovyShell(); } @@ -52,7 +55,7 @@ public void cachePetriNetFunctions(PetriNet petriNet) { .map(function -> CachedFunction.build(shell, function)) .collect(Collectors.toList()); - Cache namespaceFunctionsCache = cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS); + Cache namespaceFunctionsCache = cacheManager.getCache(properties.getNamespaceFunctions()); if (!functions.isEmpty()) { evaluateCachedFunctions(functions); @@ -64,7 +67,7 @@ public void cachePetriNetFunctions(PetriNet petriNet) { @Override public void reloadCachedFunctions(String petriNetId) { - cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).evictIfPresent(petriNetId); + cacheManager.getCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId); cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId)); } @@ -144,7 +147,7 @@ private String stringifyParameterTypes(Class[] a) { @Override public Map> getNamespaceFunctionCache() { - return new HashMap<>((Map) cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).getNativeCache()); + return new HashMap<>((Map) cacheManager.getCache(properties.getNamespaceFunctions()).getNativeCache()); } @Override @@ -154,7 +157,7 @@ public void clearActionCache() { @Override public void clearNamespaceFunctionCache() { - cacheManager.getCache(CacheMapKeys.NAMESPACE_FUNCTIONS).clear(); + cacheManager.getCache(properties.getNamespaceFunctions()).clear(); } @Override From f9796d66606a50b6d457df16f3769ef354961a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Tue, 16 Sep 2025 11:22:37 +0200 Subject: [PATCH 07/28] NAE-2182 - removal of unused methods, correction of several issues due to changes in cache structure --- .../engine/configuration/CacheMapKeys.java | 3 ++- .../engine/configuration/GenericMapCache.java | 20 +++++++------------ .../petrinet/service/PetriNetService.java | 13 ------------ .../service/interfaces/IPetriNetService.java | 11 ---------- .../workflow/domain/CachedFunction.java | 16 --------------- 5 files changed, 9 insertions(+), 54 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java index c99f7a3985..f5db1e619e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.configuration; -public class CacheMapKeys { +public final class CacheMapKeys { + private CacheMapKeys() {} public static final String ACTIONS = "actions"; public static final String FUNCTIONS = "functions"; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 44ac966014..a31ef3e511 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -12,13 +12,13 @@ @Slf4j public class GenericMapCache implements Cache { private final String name; - protected Class valueType; + private final Class valueType; private final java.util.function.Supplier> mapFactory; private final AtomicReference> atomicMapRef; private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); - public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory) { + public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory) { this.name = name; this.valueType = valueType; this.mapFactory = mapFactory; @@ -30,7 +30,6 @@ public GenericMapCache(String name, Class valueType, java.util.function.Suppl @Override public Object getNativeCache() { return Map.copyOf(map()); } @Override - @SuppressWarnings("unchecked") public T get(Object key, Callable loader) { final String stringKey = String.valueOf(key); @@ -57,7 +56,7 @@ public T get(Object key, Callable loader) { return (T) value; } } catch (Exception ex) { - throw new ValueRetrievalException(stringKey, loader, ex); + throw new Cache.ValueRetrievalException(stringKey, loader, ex); } finally { locks.remove(stringKey, lock); } @@ -66,13 +65,8 @@ public T get(Object key, Callable loader) { @Override public synchronized ValueWrapper get(Object key) { String stringKey = String.valueOf(key); - Object valueObject = map().get(stringKey); - if (valueObject != null) { - return new SimpleValueWrapper(valueObject); - } - - return new SimpleValueWrapper(null); + return valueObject != null ? new SimpleValueWrapper(valueObject) : null; } @Override @@ -101,14 +95,14 @@ public synchronized void evict(Object key) { @Override public synchronized void clear() { this.atomicMapRef.set(mapFactory.get()); - log.info("{} cache cleared", this.getName()); } - protected Map map() { + private Map map() { return atomicMapRef.get(); } - protected V safeCast(Object object) { + @SuppressWarnings("unchecked") + private V safeCast(Object object) { if (object == null) { return null; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index 0aa54baa2b..bb63fc3f4e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -555,19 +555,6 @@ public void runActions(List actions, PetriNet petriNet) { }); } - @Override - public Function findByFunctionId(String functionId) { - Query query = new Query(); - - query.addCriteria(Criteria.where("functions._id").is(new ObjectId(functionId))); - - PetriNet petriNet = mongoTemplate.findOne(query, PetriNet.class, "petriNet"); - - Optional optionalFunction = petriNet.getFunctions().stream().filter(function -> function.getObjectId().toString().equals(functionId)).findFirst(); - - return optionalFunction.orElse(null); - } - protected T requireNonNull(T obj, Object... item) { if (obj == null) { if (item.length > 0) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java index d3d175fdb7..5b0a63a853 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java @@ -332,15 +332,4 @@ static DataFieldReference transformToReference(PetriNet net, Transition transiti * @return a {@link PetriNetImportReference} linking the PetriNet */ PetriNetImportReference getNetFromCase(String caseId); - - - /** - * Finds and returns a single function subdocument from the {@code petriNet} collection - * by its nested {@code functions._id}. - * - * @param functionId the string form of the function's ObjectId (24-hex) - * @return the matching {@code Function} subdocument, or {@code null} if not found - * @throws IllegalArgumentException if {@code functionId} is not a valid ObjectId - */ - Function findByFunctionId(String functionId); } \ No newline at end of file diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java index 139b7c8933..55a6b3f890 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/domain/CachedFunction.java @@ -23,20 +23,4 @@ public static CachedFunction build(GroovyShell shell, Function function) { .function(function) .build(); } - - public static CachedFunction copyOf(GroovyShell shell, CachedFunction cachedFunction) { - if (cachedFunction == null) { - return null; - } - - Closure code = (Closure) shell.evaluate(cachedFunction.getFunction().getDefinition()); - if (code == null) { - throw new IllegalArgumentException("Non compilable function"); - } - - return CachedFunction.builder() - .code(code) - .function(new Function(cachedFunction.getFunction())) - .build(); - } } From d2227d0975a89a6816c9aa899e8f9b47d9588686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Thu, 25 Sep 2025 21:41:08 +0200 Subject: [PATCH 08/28] NAE-2182 - optimized imports, added helper and refactor code --- .../configuration/CacheConfiguration.java | 7 ++---- .../engine/configuration/GenericMapCache.java | 10 ++------ .../petrinet/service/PetriNetService.java | 6 +++-- .../service/FieldActionsCacheService.java | 24 ++++++++++++------- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 4d8849fcfc..7a54194073 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -5,6 +5,7 @@ import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; import com.netgrif.application.engine.workflow.domain.CachedFunction; import groovy.lang.Closure; +import lombok.RequiredArgsConstructor; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; @@ -22,15 +23,11 @@ @Configuration @EnableCaching +@RequiredArgsConstructor public class CacheConfiguration extends CachingConfigurerSupport { private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties; private final CacheConfigurationProperties properties; - public CacheConfiguration(RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties, CacheConfigurationProperties properties) { - this.fieldRunnerProperties = fieldRunnerProperties; - this.properties = properties; - } - @Bean @Primary @Override diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index a31ef3e511..d2a12aecc1 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -63,7 +63,7 @@ public T get(Object key, Callable loader) { } @Override - public synchronized ValueWrapper get(Object key) { + public ValueWrapper get(Object key) { String stringKey = String.valueOf(key); Object valueObject = map().get(stringKey); return valueObject != null ? new SimpleValueWrapper(valueObject) : null; @@ -73,13 +73,7 @@ public synchronized ValueWrapper get(Object key) { public synchronized T get(Object key, Class type) { String stringKey = String.valueOf(key); Object valueObject = map().get(stringKey); - - if (valueObject != null) { - return type.cast(valueObject); - } - - return type.cast(null); - + return valueObject != null ? type.cast(valueObject) : null; } @Override diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index bb63fc3f4e..491dd2c5de 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -5,10 +5,12 @@ import com.netgrif.application.engine.objects.auth.domain.ActorTransformer; import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.files.minio.StorageConfigurationProperties; -import com.netgrif.application.engine.objects.petrinet.domain.*; +import com.netgrif.application.engine.objects.petrinet.domain.PetriNet; +import com.netgrif.application.engine.objects.petrinet.domain.PetriNetSearch; +import com.netgrif.application.engine.objects.petrinet.domain.Transition; +import com.netgrif.application.engine.objects.petrinet.domain.VersionType; import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService; import com.netgrif.application.engine.petrinet.web.responsebodies.ArcImportReference; -import com.netgrif.application.engine.objects.auth.domain.Group; import com.netgrif.application.engine.objects.auth.domain.LoggedUser; import com.netgrif.application.engine.auth.service.UserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticPetriNetMappingService; diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index b0f7317bb5..0b91ddc5c0 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -55,7 +55,7 @@ public void cachePetriNetFunctions(PetriNet petriNet) { .map(function -> CachedFunction.build(shell, function)) .collect(Collectors.toList()); - Cache namespaceFunctionsCache = cacheManager.getCache(properties.getNamespaceFunctions()); + Cache namespaceFunctionsCache = getRequiredCache(properties.getNamespaceFunctions()); if (!functions.isEmpty()) { evaluateCachedFunctions(functions); @@ -67,7 +67,7 @@ public void cachePetriNetFunctions(PetriNet petriNet) { @Override public void reloadCachedFunctions(String petriNetId) { - cacheManager.getCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId); + getRequiredCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId); cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId)); } @@ -79,7 +79,7 @@ public void reloadCachedFunctions(PetriNet petriNet) { @Override public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) { String stringId = action.getId().toString(); - Cache actionsCache = cacheManager.getCache(CacheMapKeys.ACTIONS); + Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS); Object nativeActionsCache = actionsCache.getNativeCache(); if (nativeActionsCache instanceof Map map) { @@ -94,7 +94,7 @@ public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActio @Override public List getCachedFunctions(List functions) { List cachedFunctions = new ArrayList<>(functions.size()); - Cache functionsCache = cacheManager.getCache(CacheMapKeys.FUNCTIONS); + Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); Object nativeFunctionsCache = functionsCache.getNativeCache(); if (nativeFunctionsCache instanceof Map map) { @@ -147,21 +147,29 @@ private String stringifyParameterTypes(Class[] a) { @Override public Map> getNamespaceFunctionCache() { - return new HashMap<>((Map) cacheManager.getCache(properties.getNamespaceFunctions()).getNativeCache()); + return new HashMap<>((Map) getRequiredCache(properties.getNamespaceFunctions()).getNativeCache()); } @Override public void clearActionCache() { - cacheManager.getCache(CacheMapKeys.ACTIONS).clear(); + getRequiredCache(CacheMapKeys.ACTIONS).clear(); } @Override public void clearNamespaceFunctionCache() { - cacheManager.getCache(properties.getNamespaceFunctions()).clear(); + getRequiredCache(properties.getNamespaceFunctions()).clear(); } @Override public void clearFunctionCache() { - cacheManager.getCache(CacheMapKeys.FUNCTIONS).clear(); + getRequiredCache(CacheMapKeys.FUNCTIONS).clear(); + } + + private Cache getRequiredCache(String name) { + Cache cache = cacheManager.getCache(name); + if (cache == null) { + throw new IllegalStateException("Cache '" + name + "' is not configured"); + } + return cache; } } From f7b61be7b113679023abe726a3c81e9b1c4380ae Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Thu, 18 Dec 2025 13:53:29 +0100 Subject: [PATCH 09/28] Refactor caching logic in FieldActionsCacheService and improve annotations. Updated method names and cache references to align with global function terminology, ensuring consistency across the service. Added proper type annotations in GenericMapCache and replaced `CachingConfigurerSupport` with `CachingConfigurer` for better configuration clarity and flexibility. --- .../engine/configuration/CacheConfiguration.java | 4 ++-- .../engine/configuration/GenericMapCache.java | 5 +++-- .../workflow/service/FieldActionsCacheService.java | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 41fc847914..bce3b543d9 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.CachingConfigurer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.cache.interceptor.CacheResolver; @@ -25,7 +25,7 @@ @Configuration @EnableCaching @RequiredArgsConstructor -public class CacheConfiguration extends CachingConfigurerSupport { +public class CacheConfiguration implements CachingConfigurer { private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties; private final CacheConfigurationProperties properties; diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index d2a12aecc1..199700182e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.configuration; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; @@ -25,9 +26,9 @@ public GenericMapCache(String name, Class valueType, java.util.function.Suppl this.atomicMapRef = new AtomicReference<>(mapFactory.get()); } - @Override public String getName() { return name; } + @Override public @NotNull String getName() { return name; } - @Override public Object getNativeCache() { return Map.copyOf(map()); } + @Override public @NotNull Object getNativeCache() { return Map.copyOf(map()); } @Override public T get(Object key, Callable loader) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 9c1450fd8d..7b8de9db8e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -58,20 +58,20 @@ public void cachePetriNetFunctions(PetriNet petriNet) { .map(function -> CachedFunction.build(shell, function)) .collect(Collectors.toList()); - Cache namespaceFunctionsCache = getRequiredCache(properties.getNamespaceFunctions()); + Cache globalFunctionsCache = getRequiredCache(properties.getGlobalFunctions()); if (!functions.isEmpty()) { evaluateCachedFunctions(functions); globalFunctionsCache.put(petriNet.getIdentifier(), functions); } else { - globalFunctionsCache.remove(petriNet.getIdentifier()); + globalFunctionsCache.evictIfPresent(petriNet.getIdentifier()); } } @Override public void reloadCachedFunctions(String petriNetId) { - getRequiredCache(properties.getNamespaceFunctions()).evictIfPresent(petriNetId); - cachePetriNetFunctions(petriNetService.getNewestVersionByIdentifier(petriNetId)); + getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId); + cachePetriNetFunctions(petriNetService.getActiveVersionByIdentifier(petriNetId)); } @Override @@ -172,7 +172,7 @@ private String stringifyParameterTypes(Class[] a) { @Override public Map> getGlobalFunctionsCache() { - return new HashMap<>((Map) getRequiredCache(properties.getGlobalFunctions()).getNativeCache()); + return new HashMap<>((Map>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache()); } @Override From 749db251386fa98c5d5f5a99d56b8629d74b0f04 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Fri, 9 Jan 2026 08:23:26 +0100 Subject: [PATCH 10/28] Update function to use the default Petri net version Replaced the method call for retrieving the active version of a Petri net with a call to fetch the default version. This ensures consistency in caching behavior by aligning it with the default Petri net configuration. --- .../engine/workflow/service/FieldActionsCacheService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 7b8de9db8e..4a22d26bce 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -71,7 +71,7 @@ public void cachePetriNetFunctions(PetriNet petriNet) { @Override public void reloadCachedFunctions(String petriNetId) { getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId); - cachePetriNetFunctions(petriNetService.getActiveVersionByIdentifier(petriNetId)); + cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId)); } @Override From d573cf275ec54c641ebb4b6bb5e6f566fbbdc5c0 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Fri, 9 Jan 2026 09:12:54 +0100 Subject: [PATCH 11/28] Refactor function caching property configuration Moved `functionCachingPageSize` from `RunnerConfigurationProperties` to `CacheConfigurationProperties` to improve property organization and maintainability. This ensures related cache settings are grouped together for better clarity. --- .../properties/CacheConfigurationProperties.java | 7 +++++++ .../properties/RunnerConfigurationProperties.java | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java index 241bc2f064..419c0835c8 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java @@ -57,6 +57,13 @@ public class CacheConfigurationProperties { */ private List additional = new ArrayList<>(); + /** + * The size of pages used for caching functions when processing large sets of data. + * This property determines the maximum number of functions to include in a single page during caching operations. + * Default value is 500. + */ + private int functionCachingPageSize = 500; + /** * Retrieves a set of all configured cache names. * Includes the default caches and any additional user-defined cache names. diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java index 921cab391c..03f4bcb0a9 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java @@ -57,14 +57,6 @@ public static class FieldRunnerProperties { * The size of the cache used for managing global Petri net functions. */ private int globalFunctionsCacheSize = 500; - - - /** - * The size of pages used for caching functions when processing large sets of data. - * This property determines the maximum number of functions to include in a single page during caching operations. - * Default value is 500. - */ - private int functionCachingPageSize = 500; } /** From 4c1cf755b86921372fe3498f65c711e1c0d8a239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Voz=C3=A1r?= Date: Fri, 9 Jan 2026 13:55:54 +0100 Subject: [PATCH 12/28] [NAE-2182] Refactor GenericMapCache to use ConcurrentHashMap.computeIfAbsent and remove mixed synchronization --- .../configuration/CacheConfiguration.java | 14 +--- .../engine/configuration/GenericMapCache.java | 71 +++++++------------ 2 files changed, 27 insertions(+), 58 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index bce3b543d9..bad4472434 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -2,7 +2,6 @@ import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; import com.netgrif.application.engine.configuration.properties.RunnerConfigurationProperties; -import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; import com.netgrif.application.engine.workflow.domain.CachedFunction; import groovy.lang.Closure; import lombok.RequiredArgsConstructor; @@ -19,7 +18,6 @@ import java.util.*; -import java.util.function.Supplier; import java.util.stream.Collectors; @Configuration @@ -38,23 +36,15 @@ public CacheManager cacheManager() { .map(ConcurrentMapCache::new) .collect(Collectors.toCollection(ArrayList::new)); - - Supplier> actionsFactory = - () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize())); - caches.add(new GenericMapCache<>( CacheMapKeys.ACTIONS, Closure.class, - actionsFactory + fieldRunnerProperties.getActionCacheSize() )); - - Supplier> functionsFactory = - () -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize())); - caches.add(new GenericMapCache<>( CacheMapKeys.FUNCTIONS, CachedFunction.class, - functionsFactory + fieldRunnerProperties.getFunctionsCacheSize() )); SimpleCacheManager cacheManager = new SimpleCacheManager(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 199700182e..1035cf311b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -8,92 +8,71 @@ import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; @Slf4j public class GenericMapCache implements Cache { private final String name; private final Class valueType; - private final java.util.function.Supplier> mapFactory; - private final AtomicReference> atomicMapRef; + private final ConcurrentHashMap map; - private final ConcurrentHashMap locks = new ConcurrentHashMap<>(); - - public GenericMapCache(String name, Class valueType, java.util.function.Supplier> mapFactory) { + public GenericMapCache(String name, Class valueType, int cacheSize) { this.name = name; this.valueType = valueType; - this.mapFactory = mapFactory; - this.atomicMapRef = new AtomicReference<>(mapFactory.get()); + this.map = new ConcurrentHashMap<>(cacheSize); } @Override public @NotNull String getName() { return name; } - @Override public @NotNull Object getNativeCache() { return Map.copyOf(map()); } + @Override public @NotNull Object getNativeCache() { return Map.copyOf(map); } @Override public T get(Object key, Callable loader) { final String stringKey = String.valueOf(key); - - Object mapValue = map().get(stringKey); - if (mapValue != null) { - return (T) mapValue; - } - - Object lock = locks.computeIfAbsent(stringKey, lockKey -> new Object()); try { - synchronized (lock) { - Object mapLockValue = map().get(stringKey); - if (mapLockValue != null) { - return (T) mapLockValue; + V value = map.computeIfAbsent(stringKey, cacheValue -> { + try { + T computed = loader.call(); + if (computed == null) return null; + return safeCast(computed); } - - T computed = loader.call(); - if (computed == null) { - return null; + catch (Exception e) { + throw new RuntimeException(e); } - - V value = safeCast(computed); - map().put(stringKey, value); - return (T) value; - } - } catch (Exception ex) { - throw new Cache.ValueRetrievalException(stringKey, loader, ex); - } finally { - locks.remove(stringKey, lock); + }); + return (T) value; + } catch (RuntimeException e) { + Throwable cause = (e.getCause() != null) ? e.getCause() : e; + throw new Cache.ValueRetrievalException(stringKey, loader, cause); } } @Override public ValueWrapper get(Object key) { String stringKey = String.valueOf(key); - Object valueObject = map().get(stringKey); + Object valueObject = map.get(stringKey); return valueObject != null ? new SimpleValueWrapper(valueObject) : null; } @Override - public synchronized T get(Object key, Class type) { + public T get(Object key, Class type) { String stringKey = String.valueOf(key); - Object valueObject = map().get(stringKey); + Object valueObject = map.get(stringKey); return valueObject != null ? type.cast(valueObject) : null; } @Override - public synchronized void put(Object key, Object value) { - map().put(String.valueOf(key), safeCast(value)); + public void put(Object key, Object value) { + map.put(String.valueOf(key), safeCast(value)); } @Override - public synchronized void evict(Object key) { - map().remove(String.valueOf(key)); + public void evict(Object key) { + map.remove(String.valueOf(key)); } @Override - public synchronized void clear() { - this.atomicMapRef.set(mapFactory.get()); - } - - private Map map() { - return atomicMapRef.get(); + public void clear() { + map.clear(); } @SuppressWarnings("unchecked") From 376da580836ee9cdf9b53843152af4807233e8f8 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 17:24:35 +0100 Subject: [PATCH 13/28] - clarified error message in createCaseByIdentifier - corrected not valid HTML --- .../application/engine/workflow/service/WorkflowService.java | 2 +- .../engine/objects/petrinet/domain/version/Version.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java index 162c76b044..218e6facfa 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowService.java @@ -287,7 +287,7 @@ public CreateCaseEventOutcome createCase(String netId, String title, String colo public CreateCaseEventOutcome createCaseByIdentifier(String identifier, String title, String color, LoggedUser user, Locale locale, Map params) { PetriNet net = petriNetService.getDefaultVersionByIdentifier(identifier); if (net == null) { - throw new IllegalArgumentException("Petri net with identifier [" + identifier + "] does not exist."); + throw new IllegalArgumentException("Petri net with identifier [" + identifier + "] does not have default version."); } return this.createCase(net.getStringId(), title != null && !title.equals("") ? title : net.getDefaultCaseName().getTranslation(locale), color, user, params); } diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java index 50dd1f5481..2f664e4d03 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java @@ -56,7 +56,7 @@ public void increment(VersionType type) { * Compares this version to the other version * * @param other other version to be compared with - * @return 0 if the versions equal, <0 if this is lower than other, >0 if this is higher than other + * @return 0 if the versions equal, < 0 if this is lower than other, > 0 if this is higher than other */ public int compareTo(Version other) { if (this.major != other.major) { From dc1394bab437e21d9d1a5a457e3885a13e25c4fa Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 17:30:49 +0100 Subject: [PATCH 14/28] - corrected properties error according to rabbit ai --- .../configuration/CacheConfiguration.java | 10 ++++--- .../engine/configuration/CacheMapKeys.java | 1 + .../CacheConfigurationProperties.java | 22 ++++++++++++++- .../RunnerConfigurationProperties.java | 27 ------------------- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index bad4472434..8f0b1cc81d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -24,7 +24,6 @@ @EnableCaching @RequiredArgsConstructor public class CacheConfiguration implements CachingConfigurer { - private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties; private final CacheConfigurationProperties properties; @Bean @@ -39,12 +38,17 @@ public CacheManager cacheManager() { caches.add(new GenericMapCache<>( CacheMapKeys.ACTIONS, Closure.class, - fieldRunnerProperties.getActionCacheSize() + properties.getActionCacheSize() )); caches.add(new GenericMapCache<>( CacheMapKeys.FUNCTIONS, CachedFunction.class, - fieldRunnerProperties.getFunctionsCacheSize() + properties.getFunctionsCacheSize() + )); + caches.add(new GenericMapCache<>( + CacheMapKeys.GLOBAL_FUNCTIONS, + CachedFunction.class, + properties.getGlobalFunctionsCacheSize() )); SimpleCacheManager cacheManager = new SimpleCacheManager(); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java index f5db1e619e..bb98f26977 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheMapKeys.java @@ -4,4 +4,5 @@ public final class CacheMapKeys { private CacheMapKeys() {} public static final String ACTIONS = "actions"; public static final String FUNCTIONS = "functions"; + public static final String GLOBAL_FUNCTIONS = "globalFunctions"; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java index 419c0835c8..80b55861b6 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.configuration.properties; +import jakarta.validation.constraints.Min; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -62,8 +63,27 @@ public class CacheConfigurationProperties { * This property determines the maximum number of functions to include in a single page during caching operations. * Default value is 500. */ + @Min(1) private int functionCachingPageSize = 500; + /** + * The size of the cache used for handling field runner actions. + */ + @Min(1) + private int actionCacheSize = 500; + + /** + * The size of the cache used for managing field runner functions. + */ + @Min(1) + private int functionsCacheSize = 500; + + /** + * The size of the cache used for managing global Petri net functions. + */ + @Min(1) + private int globalFunctionsCacheSize = 500; + /** * Retrieves a set of all configured cache names. * Includes the default caches and any additional user-defined cache names. @@ -72,7 +92,7 @@ public class CacheConfigurationProperties { */ public Set getAllCaches() { Set caches = new LinkedHashSet<>(Arrays.asList(petriNetById, petriNetByIdentifier, petriNetDefault, - petriNetLatest, petriNetCache, loadedModules, globalFunctions)); + petriNetLatest, petriNetCache, loadedModules)); caches.addAll(additional); return caches; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java index 03f4bcb0a9..5e48be820b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/RunnerConfigurationProperties.java @@ -17,11 +17,6 @@ public class RunnerConfigurationProperties { */ private ExpressionRunnerProperties expressionRunner = new ExpressionRunnerProperties(); - /** - * Configuration for the field runner, including action, function, and namespace cache sizes. - */ - private FieldRunnerProperties fieldRunner = new FieldRunnerProperties(); - /** * Configuration specific to the expression runner component. */ @@ -36,28 +31,6 @@ public static class ExpressionRunnerProperties { } - /** - * Configuration specific to the field runner component. - */ - @Data - @ConfigurationProperties(prefix = "netgrif.engine.runner.field-runner") - public static class FieldRunnerProperties { - - /** - * The size of the cache used for handling field runner actions. - */ - private int actionCacheSize = 500; - - /** - * The size of the cache used for managing field runner functions. - */ - private int functionsCacheSize = 500; - - /** - * The size of the cache used for managing global Petri net functions. - */ - private int globalFunctionsCacheSize = 500; - } /** * Configuration specific to the application runner component. From 299c4f13579c2a6bedb502c5a271299b753648fb Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 17:33:27 +0100 Subject: [PATCH 15/28] - removed trailing white space from functions.md --- docs/process/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/process/functions.md b/docs/process/functions.md index 3d1eb4dcd1..a597343760 100644 --- a/docs/process/functions.md +++ b/docs/process/functions.md @@ -4,7 +4,7 @@ This document contains information about new Petriflow functions introduced in N ## Overview -Petriflow functions provide opportunity to declare your own functions without any changes in applications code. +Petriflow functions provide opportunity to declare your own functions without any changes in applications code. ### Scopes From a54ea6d9c32120203fde15fa163046a489815254 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 17:34:16 +0100 Subject: [PATCH 16/28] - clarified documentation --- .../configuration/properties/CacheConfigurationProperties.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java index 80b55861b6..595c318afa 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java @@ -48,7 +48,7 @@ public class CacheConfigurationProperties { private String loadedModules = "loadedModules"; /** - * Default cache name for caching global functions of PetriNet global scoped functions. + * Default cache name for caching PetriNet global-scoped functions. */ private String globalFunctions = "globalFunctions"; From 21b597f1800a68aa754dfc010d23881f630f1691 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 17:53:43 +0100 Subject: [PATCH 17/28] - added check for existing petri net version - added check for existing cache value --- .../petrinet/service/PetriNetService.java | 2 +- .../service/FieldActionsCacheService.java | 52 +++++++++++-------- .../interfaces/IFieldActionsCacheService.java | 4 +- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java index c50112d65d..40c90ca20c 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java @@ -633,7 +633,7 @@ protected void deletePetriNet(String processId, LoggedUser loggedUser, boolean f publisher.publishEvent(new ProcessDeleteEvent(petriNet, EventPhase.PRE)); repository.deleteBy_id(petriNet.getObjectId()); evictCache(petriNet); - functionCacheService.reloadCachedFunctions(petriNet); + functionCacheService.reloadCachedGlobalFunctions(petriNet); if (petriNet.isDefaultVersion()) { PetriNet processToMakeDefault = self.getLatestVersionByIdentifier(petriNet.getIdentifier()); if (processToMakeDefault != null) { diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index e6d739af34..1cd817a44c 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -69,27 +69,29 @@ public void cachePetriNetFunctions(PetriNet petriNet) { } @Override - public void reloadCachedFunctions(String petriNetId) { - getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId); - cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId)); + public void reloadCachedGlobalFunctions(String petriNetId) { + PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(petriNetId); + if (petriNet != null) { + getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId); + cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId)); + } } @Override - public void reloadCachedFunctions(PetriNet petriNet) { - this.reloadCachedFunctions(petriNet.getIdentifier()); + public void reloadCachedGlobalFunctions(PetriNet petriNet) { + this.reloadCachedGlobalFunctions(petriNet.getIdentifier()); } @Override public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions) { String stringId = action.getId().toString(); Cache actionsCache = getRequiredCache(CacheMapKeys.ACTIONS); - Object nativeActionsCache = actionsCache.getNativeCache(); - if (nativeActionsCache instanceof Map map) { - if (shouldRewriteCachedActions || !map.containsKey(stringId) ) { - Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); - actionsCache.put(stringId, code); - } + Cache.ValueWrapper wrapper = actionsCache.get(stringId); + if (shouldRewriteCachedActions || wrapper == null) { + Closure code = (Closure) shell.evaluate("{-> " + action.getDefinition() + "}"); + actionsCache.put(stringId, code); + return code; } return (Closure) actionsCache.get(stringId).get(); } @@ -98,15 +100,17 @@ public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActio public List getCachedFunctions(List functions) { List cachedFunctions = new ArrayList<>(functions.size()); Cache functionsCache = getRequiredCache(CacheMapKeys.FUNCTIONS); - Object nativeFunctionsCache = functionsCache.getNativeCache(); - - if (nativeFunctionsCache instanceof Map map) { - functions.forEach(function -> { - if (!map.containsKey(function.getStringId())) { - functionsCache.put(function.getStringId(), CachedFunction.build(shell, function)); - } - cachedFunctions.add((CachedFunction) functionsCache.get(function.getStringId()).get()); - }); + + for (Function function : functions) { + Cache.ValueWrapper wrapper = functionsCache.get(function.getStringId()); + CachedFunction cached; + if (wrapper == null) { + cached = CachedFunction.build(shell, function); + functionsCache.put(function.getStringId(), cached); + } else { + cached = (CachedFunction) wrapper.get(); + } + cachedFunctions.add(cached); } return cachedFunctions; } @@ -172,7 +176,13 @@ private String stringifyParameterTypes(Class[] a) { @Override public Map> getGlobalFunctionsCache() { - return new HashMap<>((Map>) getRequiredCache(properties.getGlobalFunctions()).getNativeCache()); + Object nativeCache = getRequiredCache(properties.getGlobalFunctions()).getNativeCache(); + if (nativeCache instanceof Map map) { + @SuppressWarnings("unchecked") + Map> typedMap = (Map>) map; + return new HashMap<>(typedMap); + } + return Collections.emptyMap(); } @Override diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java index 8339be0fe6..e71f0f37a1 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IFieldActionsCacheService.java @@ -13,9 +13,9 @@ public interface IFieldActionsCacheService { void cachePetriNetFunctions(PetriNet petriNet); - void reloadCachedFunctions(String petriNetId); + void reloadCachedGlobalFunctions(String petriNetId); - void reloadCachedFunctions(PetriNet petriNet); + void reloadCachedGlobalFunctions(PetriNet petriNet); Closure getCompiledAction(Action action, boolean shouldRewriteCachedActions); From 03cb8ea40581e95f1929429c687bf549dbfdefd6 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 17:59:57 +0100 Subject: [PATCH 18/28] Refactor cache configuration and global functions handling. Replaced `properties.getGlobalFunctions()` with `CacheMapKeys.GLOBAL_FUNCTIONS` for consistency and removed unused `globalFunctions` property. Introduced `MaxSizeHashMap` for improved cache size management. Simplified method parameter names and cleaned up unused imports and definitions. --- .../engine/configuration/CacheConfiguration.java | 1 - .../engine/configuration/GenericMapCache.java | 3 +++ .../properties/CacheConfigurationProperties.java | 5 ----- .../workflow/service/FieldActionsCacheService.java | 14 +++++++------- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 8f0b1cc81d..9981432d4e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -1,7 +1,6 @@ package com.netgrif.application.engine.configuration; import com.netgrif.application.engine.configuration.properties.CacheConfigurationProperties; -import com.netgrif.application.engine.configuration.properties.RunnerConfigurationProperties; import com.netgrif.application.engine.workflow.domain.CachedFunction; import groovy.lang.Closure; import lombok.RequiredArgsConstructor; diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 1035cf311b..7e087fbbd5 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -1,10 +1,12 @@ package com.netgrif.application.engine.configuration; +import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; +import java.util.Collections; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; @@ -19,6 +21,7 @@ public GenericMapCache(String name, Class valueType, int cacheSize) { this.name = name; this.valueType = valueType; this.map = new ConcurrentHashMap<>(cacheSize); + Collections.synchronizedMap(new MaxSizeHashMap<>(cacheSize)); } @Override public @NotNull String getName() { return name; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java index 595c318afa..9adfedd1c8 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/CacheConfigurationProperties.java @@ -47,11 +47,6 @@ public class CacheConfigurationProperties { */ private String loadedModules = "loadedModules"; - /** - * Default cache name for caching PetriNet global-scoped functions. - */ - private String globalFunctions = "globalFunctions"; - /** * A list of additional custom cache names. * Allows users to define their own cache names for specific use cases. diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 1cd817a44c..1f14c890f9 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -58,7 +58,7 @@ public void cachePetriNetFunctions(PetriNet petriNet) { .map(function -> CachedFunction.build(shell, function)) .collect(Collectors.toList()); - Cache globalFunctionsCache = getRequiredCache(properties.getGlobalFunctions()); + Cache globalFunctionsCache = getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS); if (!functions.isEmpty()) { evaluateCachedFunctions(functions); @@ -69,11 +69,11 @@ public void cachePetriNetFunctions(PetriNet petriNet) { } @Override - public void reloadCachedGlobalFunctions(String petriNetId) { - PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(petriNetId); + public void reloadCachedGlobalFunctions(String processIdentifier) { + PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(processIdentifier); if (petriNet != null) { - getRequiredCache(properties.getGlobalFunctions()).evictIfPresent(petriNetId); - cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(petriNetId)); + getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).evictIfPresent(processIdentifier); + cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(processIdentifier)); } } @@ -176,7 +176,7 @@ private String stringifyParameterTypes(Class[] a) { @Override public Map> getGlobalFunctionsCache() { - Object nativeCache = getRequiredCache(properties.getGlobalFunctions()).getNativeCache(); + Object nativeCache = getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).getNativeCache(); if (nativeCache instanceof Map map) { @SuppressWarnings("unchecked") Map> typedMap = (Map>) map; @@ -192,7 +192,7 @@ public void clearActionCache() { @Override public void clearGlobalFunctionCache() { - getRequiredCache(properties.getGlobalFunctions()).clear(); + getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).clear(); } @Override From 173592d410147b11e3ee93d69bf840fa53994e16 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 18:16:15 +0100 Subject: [PATCH 19/28] Refactor cache implementation and optimize method usage Replaced `ConcurrentHashMap` with synchronized `MaxSizeHashMap` in `GenericMapCache` for better controlled memory usage. Simplified redundant method calls in `FieldActionsCacheService` to improve code readability and efficiency. --- .../application/engine/configuration/GenericMapCache.java | 6 ++---- .../engine/workflow/service/FieldActionsCacheService.java | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 7e087fbbd5..6d7430ba5a 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -9,19 +9,17 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; @Slf4j public class GenericMapCache implements Cache { private final String name; private final Class valueType; - private final ConcurrentHashMap map; + private final Map map; public GenericMapCache(String name, Class valueType, int cacheSize) { this.name = name; this.valueType = valueType; - this.map = new ConcurrentHashMap<>(cacheSize); - Collections.synchronizedMap(new MaxSizeHashMap<>(cacheSize)); + this.map = Collections.synchronizedMap(new MaxSizeHashMap<>(cacheSize)); } @Override public @NotNull String getName() { return name; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 1f14c890f9..0e711b4539 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -73,7 +73,7 @@ public void reloadCachedGlobalFunctions(String processIdentifier) { PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(processIdentifier); if (petriNet != null) { getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).evictIfPresent(processIdentifier); - cachePetriNetFunctions(petriNetService.getDefaultVersionByIdentifier(processIdentifier)); + cachePetriNetFunctions(petriNet); } } @@ -93,7 +93,7 @@ public Closure getCompiledAction(Action action, boolean shouldRewriteCachedActio actionsCache.put(stringId, code); return code; } - return (Closure) actionsCache.get(stringId).get(); + return (Closure) wrapper.get(); } @Override From 8604c8550a436cec65e1d15ca9b23c30b245bf18 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 18:40:12 +0100 Subject: [PATCH 20/28] Refactor caching mechanism for improved concurrency and logic. Replaced synchronized map with ConcurrentHashMap in GenericMapCache for thread-safety and ensured proper caching logic by reordering conditionals in FieldActionsCacheService. These changes enhance performance and maintainability of the caching system. --- .../application/engine/configuration/GenericMapCache.java | 7 +++---- .../engine/workflow/service/FieldActionsCacheService.java | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 6d7430ba5a..1035cf311b 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -1,25 +1,24 @@ package com.netgrif.application.engine.configuration; -import com.netgrif.application.engine.elastic.service.executors.MaxSizeHashMap; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; -import java.util.Collections; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; @Slf4j public class GenericMapCache implements Cache { private final String name; private final Class valueType; - private final Map map; + private final ConcurrentHashMap map; public GenericMapCache(String name, Class valueType, int cacheSize) { this.name = name; this.valueType = valueType; - this.map = Collections.synchronizedMap(new MaxSizeHashMap<>(cacheSize)); + this.map = new ConcurrentHashMap<>(cacheSize); } @Override public @NotNull String getName() { return name; } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 0e711b4539..e5c99e86e5 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -71,8 +71,8 @@ public void cachePetriNetFunctions(PetriNet petriNet) { @Override public void reloadCachedGlobalFunctions(String processIdentifier) { PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(processIdentifier); + getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).evictIfPresent(processIdentifier); if (petriNet != null) { - getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).evictIfPresent(processIdentifier); cachePetriNetFunctions(petriNet); } } From 790b35928b37973616404028cede1cfdf4bbaab7 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 18:40:59 +0100 Subject: [PATCH 21/28] Handle null PetriNet in reloadCachedGlobalFunctions Add a null check to prevent potential NullPointerException when calling reloadCachedGlobalFunctions. This ensures better stability and avoids unnecessary errors during execution. --- .../engine/workflow/service/FieldActionsCacheService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index e5c99e86e5..71908f5344 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -79,7 +79,9 @@ public void reloadCachedGlobalFunctions(String processIdentifier) { @Override public void reloadCachedGlobalFunctions(PetriNet petriNet) { - this.reloadCachedGlobalFunctions(petriNet.getIdentifier()); + if (petriNet != null) { + this.reloadCachedGlobalFunctions(petriNet.getIdentifier()); + } } @Override From b4aef9b38dd0a8a19d1c633fdd4060a3971d9351 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Tue, 13 Jan 2026 18:51:19 +0100 Subject: [PATCH 22/28] Rename cacheSize to initialCapacity in GenericMapCache Updated the constructor parameter and internal map initialization to use 'initialCapacity' instead of the ambiguous 'cacheSize'. This improves code clarity and better --- .../application/engine/configuration/GenericMapCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 1035cf311b..98f804e679 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -15,10 +15,10 @@ public class GenericMapCache implements Cache { private final Class valueType; private final ConcurrentHashMap map; - public GenericMapCache(String name, Class valueType, int cacheSize) { + public GenericMapCache(String name, Class valueType, int initialCapacity) { this.name = name; this.valueType = valueType; - this.map = new ConcurrentHashMap<>(cacheSize); + this.map = new ConcurrentHashMap<>(initialCapacity); } @Override public @NotNull String getName() { return name; } From e29724291ab58c1e6bba2662221a3e60003cd95b Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 14 Jan 2026 08:59:09 +0100 Subject: [PATCH 23/28] Handle null values in GenericMapCache.put method Added a null check in the GenericMapCache.put method to evict entries with null values instead of storing them. Updated JavaDoc in Version.java to fix a minor formatting issue. --- .../application/engine/configuration/GenericMapCache.java | 4 ++++ .../engine/objects/petrinet/domain/version/Version.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 98f804e679..20646b9eac 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -62,6 +62,10 @@ public T get(Object key, Class type) { @Override public void put(Object key, Object value) { + if (value == null) { + evict(key); + return; + } map.put(String.valueOf(key), safeCast(value)); } diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java index 2f664e4d03..2a54e10fde 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/petrinet/domain/version/Version.java @@ -56,7 +56,7 @@ public void increment(VersionType type) { * Compares this version to the other version * * @param other other version to be compared with - * @return 0 if the versions equal, < 0 if this is lower than other, > 0 if this is higher than other + * @return 0 if the versions equal, < 0 if this is lower than other, > 0 if this is higher than other */ public int compareTo(Version other) { if (this.major != other.major) { From 271c93f396e2f1e059de0de8fec4f46ff629831c Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 14 Jan 2026 09:24:15 +0100 Subject: [PATCH 24/28] Fix class cast check for List elements in GenericMapCache Previously, the cache retrieval logic did not properly handle cases where cached objects were lists. This change ensures that the type check applies to the first element of a list, preventing potential `ClassCastException` issues. --- .../application/engine/configuration/GenericMapCache.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 20646b9eac..91f08b926e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -5,6 +5,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; +import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; @@ -85,7 +86,7 @@ private V safeCast(Object object) { return null; } - if (!valueType.isInstance(object)) { + if (object instanceof List && !valueType.isInstance(((List) object).getFirst())) { throw new ClassCastException("Expected " + valueType.getName() + " but was " + object.getClass().getName()); } From 7a57310972e117437eaa17893e8a9b3afa7f74ab Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 14 Jan 2026 10:11:34 +0100 Subject: [PATCH 25/28] Refactor cache implementation to support generic element types Introduce an additional generic type parameter in `GenericMapCache` to ensure proper type handling for list elements. Update `safeCast` logic to validate element types in lists and modify related cache configurations accordingly. Remove redundant cache eviction from `FieldActionsCacheService`. --- .../configuration/CacheConfiguration.java | 4 ++ .../engine/configuration/GenericMapCache.java | 43 ++++++++++++++++--- .../service/FieldActionsCacheService.java | 1 - 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 9981432d4e..88b822db98 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -16,6 +16,7 @@ import org.springframework.context.annotation.Primary; +import java.lang.annotation.ElementType; import java.util.*; import java.util.stream.Collectors; @@ -37,15 +38,18 @@ public CacheManager cacheManager() { caches.add(new GenericMapCache<>( CacheMapKeys.ACTIONS, Closure.class, + null, properties.getActionCacheSize() )); caches.add(new GenericMapCache<>( CacheMapKeys.FUNCTIONS, CachedFunction.class, + null, properties.getFunctionsCacheSize() )); caches.add(new GenericMapCache<>( CacheMapKeys.GLOBAL_FUNCTIONS, + List.class, CachedFunction.class, properties.getGlobalFunctionsCacheSize() )); diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java index 91f08b926e..b5fc4bd7d1 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java @@ -11,14 +11,16 @@ import java.util.concurrent.ConcurrentHashMap; @Slf4j -public class GenericMapCache implements Cache { +public class GenericMapCache implements Cache { private final String name; private final Class valueType; + private final Class elementType; private final ConcurrentHashMap map; - public GenericMapCache(String name, Class valueType, int initialCapacity) { + public GenericMapCache(String name, Class valueType, Class elementType, int initialCapacity) { this.name = name; this.valueType = valueType; + this.elementType = elementType; this.map = new ConcurrentHashMap<>(initialCapacity); } @@ -81,15 +83,42 @@ public void clear() { } @SuppressWarnings("unchecked") - private V safeCast(Object object) { - if (object == null) { + private V safeCast(Object value) { + if (value == null) { return null; } - if (object instanceof List && !valueType.isInstance(((List) object).getFirst())) { - throw new ClassCastException("Expected " + valueType.getName() + " but was " + object.getClass().getName()); + if (valueType.isInstance(value)) { + return (V) value; + } + + // Check if the value is a list and the cache type is List + if (value instanceof List && List.class.isAssignableFrom(valueType)) { + List list = (List) value; + + // Check only if the list is non-empty + if (!list.isEmpty()) { + Object firstElement = list.getFirst(); + + // Validate element type + if (elementType != null && !elementType.isInstance(firstElement)) { + throw new ClassCastException( + String.format("Cannot cast list element of type %s to %s", + firstElement.getClass().getName(), + elementType.getName() + ) + ); + } + } + + return (V) list; // Safe cast to desired list type } - return (V) object; + throw new ClassCastException( + String.format("Cannot cast value of type %s to %s", + value.getClass().getName(), + valueType.getName() + ) + ); } } diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 71908f5344..c74e2ba01d 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -71,7 +71,6 @@ public void cachePetriNetFunctions(PetriNet petriNet) { @Override public void reloadCachedGlobalFunctions(String processIdentifier) { PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(processIdentifier); - getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).evictIfPresent(processIdentifier); if (petriNet != null) { cachePetriNetFunctions(petriNet); } From b0f3476ef091beebe6b681bd036e9237a10d55cb Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 14 Jan 2026 10:17:27 +0100 Subject: [PATCH 26/28] Evict global functions cache during reload. Ensure the global functions cache is cleared for the given process identifier before reloading. This prevents stale data from persisting and ensures updated functions are cached correctly. --- .../engine/workflow/service/FieldActionsCacheService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index c74e2ba01d..71908f5344 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -71,6 +71,7 @@ public void cachePetriNetFunctions(PetriNet petriNet) { @Override public void reloadCachedGlobalFunctions(String processIdentifier) { PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(processIdentifier); + getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).evictIfPresent(processIdentifier); if (petriNet != null) { cachePetriNetFunctions(petriNet); } From 07aa67f58e5b8dba363bc0b87ba9f846919d3645 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 14 Jan 2026 10:18:16 +0100 Subject: [PATCH 27/28] Remove unused import from CacheConfiguration.java The import for `ElementType` was unnecessary and has been removed to clean up the code. This change improves maintainability by keeping the imports list concise and relevant. --- .../application/engine/configuration/CacheConfiguration.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java index 88b822db98..9ec851c1bb 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/CacheConfiguration.java @@ -16,7 +16,6 @@ import org.springframework.context.annotation.Primary; -import java.lang.annotation.ElementType; import java.util.*; import java.util.stream.Collectors; From 64e0c8b1b280528e40fae28c01f5f2ea674d79c6 Mon Sep 17 00:00:00 2001 From: renczesstefan Date: Wed, 14 Jan 2026 10:21:17 +0100 Subject: [PATCH 28/28] Refactor method to optimize cache eviction sequence Reordered method logic to ensure cache eviction precedes PetriNet retrieval. This avoids potential redundant operations and ensures consistent caching behavior. No functional changes were introduced. --- .../engine/workflow/service/FieldActionsCacheService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java index 71908f5344..e477ead50e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/workflow/service/FieldActionsCacheService.java @@ -70,8 +70,8 @@ public void cachePetriNetFunctions(PetriNet petriNet) { @Override public void reloadCachedGlobalFunctions(String processIdentifier) { - PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(processIdentifier); getRequiredCache(CacheMapKeys.GLOBAL_FUNCTIONS).evictIfPresent(processIdentifier); + PetriNet petriNet = petriNetService.getDefaultVersionByIdentifier(processIdentifier); if (petriNet != null) { cachePetriNetFunctions(petriNet); }