Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,30 +1,64 @@
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.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;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
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.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Configuration
@EnableCaching
@RequiredArgsConstructor
public class CacheConfiguration extends CachingConfigurerSupport {

private final RunnerConfigurationProperties.FieldRunnerProperties fieldRunnerProperties;
private final CacheConfigurationProperties properties;

public CacheConfiguration(CacheConfigurationProperties properties) {
this.properties = properties;
}

@Bean
@Primary
@Override
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager(properties.getAllCaches().toArray(String[]::new));
Set<String> cacheNames = properties.getAllCaches();
List<Cache> caches = cacheNames.stream()
.map(ConcurrentMapCache::new)
.collect(Collectors.toCollection(ArrayList::new));


Supplier<Map<String, Closure>> actionsFactory =
() -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getActionCacheSize()));

caches.add(new GenericMapCache<>(
CacheMapKeys.ACTIONS,
Closure.class,
actionsFactory
));

Supplier<Map<String, CachedFunction>> functionsFactory =
() -> Collections.synchronizedMap(new MaxSizeHashMap<>(fieldRunnerProperties.getFunctionsCacheSize()));

caches.add(new GenericMapCache<>(
CacheMapKeys.FUNCTIONS,
CachedFunction.class,
functionsFactory
));

SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(caches);
return cacheManager;
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.netgrif.application.engine.configuration;

public final class CacheMapKeys {
private CacheMapKeys() {}
public static final String ACTIONS = "actions";
public static final String FUNCTIONS = "functions";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.netgrif.application.engine.configuration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
public class GenericMapCache<V> implements Cache {
private final String name;
private final Class<V> valueType;
private final java.util.function.Supplier<Map<String, V>> mapFactory;
private final AtomicReference<Map<String, V>> atomicMapRef;

private final ConcurrentHashMap<String, Object> locks = new ConcurrentHashMap<>();

public GenericMapCache(String name, Class<V> valueType, java.util.function.Supplier<Map<String, V>> mapFactory) {
this.name = name;
this.valueType = valueType;
this.mapFactory = mapFactory;
this.atomicMapRef = new AtomicReference<>(mapFactory.get());
}

@Override public String getName() { return name; }

@Override public Object getNativeCache() { return Map.copyOf(map()); }

Comment on lines +30 to +31
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Preserve native cache identity; clear in place.
Returning Map.copyOf breaks “native” semantics and reassigning the map on clear invalidates existing nativeCache holders. Return the backing map and clear it in place.

Apply this diff:

-    @Override public Object getNativeCache() { return Map.copyOf(map()); }
+    @Override public Object getNativeCache() { return map(); }
@@
-    public synchronized void clear() {
-        this.atomicMapRef.set(mapFactory.get());
-    }
+    public synchronized void clear() {
+        map().clear();
+        log.debug("{} cache cleared", name);
+    }

Also applies to: 95-98

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/GenericMapCache.java
around lines 30-31 (and likewise adjust lines 95-98), the getNativeCache method
currently returns Map.copyOf(map()) which breaks native cache identity; change
getNativeCache to return the backing map directly (return map()) so callers keep
the same instance. Also modify clear logic (lines ~95-98) to clear the existing
backing map in place (call map().clear()) instead of reassigning a new map
instance, preserving nativeCache holders and expected semantics.

@Override
public <T> T get(Object key, Callable<T> 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;
}

T computed = loader.call();
if (computed == null) {
return null;
}

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);
}
}

@Override
public ValueWrapper get(Object key) {
String stringKey = String.valueOf(key);
Object valueObject = map().get(stringKey);
return valueObject != null ? new SimpleValueWrapper(valueObject) : null;
}

@Override
public synchronized <T> T get(Object key, Class<T> type) {
String stringKey = String.valueOf(key);
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));
}

@Override
public synchronized void evict(Object key) {
map().remove(String.valueOf(key));
}

@Override
public synchronized void clear() {
this.atomicMapRef.set(mapFactory.get());
}

private Map<String, V> map() {
return atomicMapRef.get();
}

@SuppressWarnings("unchecked")
private 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -54,7 +59,7 @@ public class CacheConfigurationProperties {
* @return a {@link Set} of all cache names.
*/
public Set<String> getAllCaches() {
Set<String> caches = new LinkedHashSet<>(Arrays.asList(petriNetById, petriNetByIdentifier, petriNetNewest, petriNetCache, loadedModules));
Set<String> caches = new LinkedHashSet<>(Arrays.asList(petriNetById, petriNetByIdentifier, petriNetNewest, petriNetCache, loadedModules, namespaceFunctions));
caches.addAll(additional);
return caches;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +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.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;
Expand All @@ -17,10 +20,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;
Expand Down Expand Up @@ -567,4 +566,5 @@ protected <T> T requireNonNull(T obj, Object... item) {
}
return obj;
}

}
Original file line number Diff line number Diff line change
@@ -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.*;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Avoid wildcard imports; restore explicit imports.

Wildcard imports are against common style guides (and often blocked by Checkstyle), can hide accidental type collisions, and make IDE organize‑imports unstable. Please revert to explicit imports for the actually used types (PetriNet, PetriNetSearch, Transition, VersionType).

Apply:

-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;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetService.java
around line 4, replace the wildcard import
"com.netgrif.application.engine.objects.petrinet.domain.*" with explicit imports
for the actual types used (PetriNet, PetriNetSearch, Transition, VersionType) so
the file imports only those classes; update the import statements accordingly
and run organizer/IDE fix imports to ensure no other unused imports remain.

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;
Expand Down
Loading
Loading