From a3d3c1da497efbe44c623543ac240f1587de93e1 Mon Sep 17 00:00:00 2001 From: Milton Reder Date: Wed, 1 Jul 2026 15:11:39 -0400 Subject: [PATCH 1/5] DI wrapping ObjectCache for SQL object cache --- .../com/yetanalytics/hlaxapi/AppConfig.java | 9 ++ .../hlaxapi/HlaInterfaceImpl.java | 43 ++------ .../hlaxapi/InjectionHandler.java | 21 ++-- .../hlaxapi/cache/ObjectCache.java | 74 +++++++++++++ .../hlaxapi/ObjectCacheSpringContextTest.java | 58 ++++++++++ .../hlaxapi/cache/ObjectCacheTest.java | 103 ++++++++++++++++++ 6 files changed, 267 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java create mode 100644 src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java create mode 100644 src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java diff --git a/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java b/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java index 891ddc5..dceb6d6 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java +++ b/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java @@ -11,6 +11,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.yetanalytics.hlaxapi.cache.ObjectCache; import com.yetanalytics.hlaxapi.config.ConfigParser; import com.yetanalytics.hlaxapi.config.XapiConfig; @@ -40,6 +41,14 @@ public HLADecoderRegistry hlaDecoderRegistry(EncoderFactory encoderFactory) { return new HLADecoderRegistry(encoderFactory); } + @Bean(destroyMethod = "close") + public ObjectCache objectCache( + XapiConfig xapiConfig, + FOMXML fomXml, + HLADecoderRegistry decoderRegistry) { + return new ObjectCache(xapiConfig, fomXml, decoderRegistry); + } + @Bean public XapiConfig xapiConfig() { XapiConfig xapiConfig; diff --git a/src/main/java/com/yetanalytics/hlaxapi/HlaInterfaceImpl.java b/src/main/java/com/yetanalytics/hlaxapi/HlaInterfaceImpl.java index 9505aee..6da5fce 100755 --- a/src/main/java/com/yetanalytics/hlaxapi/HlaInterfaceImpl.java +++ b/src/main/java/com/yetanalytics/hlaxapi/HlaInterfaceImpl.java @@ -3,7 +3,6 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -13,8 +12,7 @@ import org.apache.logging.log4j.Logger; import com.yetanalytics.hlaxapi.cache.FomCatalog; -import com.yetanalytics.hlaxapi.cache.HlaObjectCache; -import com.yetanalytics.hlaxapi.cache.QueryReferenceCollector; +import com.yetanalytics.hlaxapi.cache.ObjectCache; import com.yetanalytics.hlaxapi.config.XapiConfig; import com.yetanalytics.hlaxapi.config.model.StatementTrigger; import com.yetanalytics.hlaxapi.injection.InteractionInjectionContext; @@ -87,23 +85,11 @@ public class HlaInterfaceImpl extends NullFederateAmbassador implements HlaInter @Autowired private SimulationConfig simulationConfig; - @Autowired - private FOMXML fomXml; - - @Autowired - private HLADecoderRegistry decoderRegistry; - @Autowired private TriggerProcessor triggerProcessor; @Autowired - private InjectionHandler injectionHandler; - - private FomCatalog fomCatalog; - - private HlaObjectCache objectCache; - - private Map> objectCacheSubscriptions = Collections.emptyMap(); + private ObjectCache objectCache; public void start() throws ConnectionFailed, InvalidLocalSettingsDesignator, RTIinternalError, NotConnected, ErrorReadingFDD, @@ -112,14 +98,8 @@ public void start() RtiFactory rtiFactory = RtiFactoryFactory.getRtiFactory(); ambassador = rtiFactory.getRtiAmbassador(); - fomCatalog = FomCatalog.fromFomXml(fomXml); - objectCacheSubscriptions = QueryReferenceCollector.collect(xapiConfig.statementTriggers); - if (objectCacheSubscriptions.isEmpty()) { - injectionHandler.setQueryService(null); + if (!objectCache.isEnabled()) { logger.info("No query injections configured; object cache is disabled"); - } else { - objectCache = new HlaObjectCache(HlaObjectCache.defaultJdbcUrl(), fomCatalog, fomXml, decoderRegistry); - injectionHandler.setQueryService(objectCache.queryService()); } try { @@ -194,11 +174,6 @@ public void stop() throws RTIinternalError { } catch (FederateIsExecutionMember | CallNotAllowedFromWithinCallback e) { throw new RTIinternalError("HlaInterfaceFailure", e); } - injectionHandler.setQueryService(null); - if (objectCache != null) { - objectCache.close(); - objectCache = null; - } } catch (NotConnected ignored) { } } @@ -214,12 +189,12 @@ public void connectionLost(String faultDescription) throws FederateInternalError private void subscribeObjectClasses() throws FederateNotExecutionMember, RestoreInProgress, SaveInProgress, NotConnected, RTIinternalError { - if (objectCacheSubscriptions.isEmpty()) { + if (!objectCache.isEnabled()) { return; } - for (Map.Entry> subscription : objectCacheSubscriptions.entrySet()) { + for (Map.Entry> subscription : objectCache.subscriptions().entrySet()) { try { - FomCatalog.ObjectClassDef clazz = fomCatalog.objectClass(subscription.getKey()).orElseThrow( + FomCatalog.ObjectClassDef clazz = objectCache.catalog().objectClass(subscription.getKey()).orElseThrow( () -> new IllegalArgumentException("No FOM object class " + subscription.getKey())); ObjectClassHandle classHandle = ambassador.getObjectClassHandle(clazz.localName()); AttributeHandleSet attributeHandles = ambassador.getAttributeHandleSetFactory().create(); @@ -251,7 +226,7 @@ public void discoverObjectInstance( ObjectClassHandle theObjectClass, String objectName, hla.rti1516e.FederateHandle producingFederate) throws FederateInternalError { - if (objectCache == null) { + if (!objectCache.isEnabled()) { return; } try { @@ -303,7 +278,7 @@ public void reflectAttributeValues( } private void reflectAttributeValues(ObjectInstanceHandle theObject, AttributeHandleValueMap theAttributes) { - if (objectCache == null) { + if (!objectCache.isEnabled()) { return; } try { @@ -356,7 +331,7 @@ public void removeObjectInstance( } private void removeCachedObject(ObjectInstanceHandle theObject) { - if (objectCache == null) { + if (!objectCache.isEnabled()) { return; } try { diff --git a/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java b/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java index 53fbd16..9963233 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java +++ b/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Component; import com.yetanalytics.hlaxapi.FOMXML.PathCheckResult; -import com.yetanalytics.hlaxapi.cache.CacheQueryService; +import com.yetanalytics.hlaxapi.cache.ObjectCache; import com.yetanalytics.hlaxapi.config.model.Criterion; import com.yetanalytics.hlaxapi.config.model.Expression; import com.yetanalytics.hlaxapi.config.model.LogicalExpression; @@ -36,7 +36,7 @@ public class InjectionHandler { private static final Logger logger = LogManager.getLogger(InjectionHandler.class); - private volatile CacheQueryService queryService; + private ObjectCache objectCache; @Autowired private FOMXML fomXml; @@ -44,8 +44,12 @@ public class InjectionHandler { @Autowired private HLADecoderRegistry hlaDecoderRegistry; - public void setQueryService(CacheQueryService cacheQueryService) { - this.queryService = cacheQueryService; + public InjectionHandler() { + } + + @Autowired + public InjectionHandler(ObjectCache objectCache) { + this.objectCache = objectCache; } public Object handleThis(Target t, InjectionContext context) { @@ -225,13 +229,12 @@ public Object handleQuery(String clazz, Target attrTarget, Expression criteria) } public Object handleQuery(String clazz, Target attrTarget, Expression criteria, InjectionContext context) { - CacheQueryService service = queryService; - if (service == null) { + if (objectCache == null) { return null; } Expression resolvedCriteria = resolveThisExpressions(criteria, context); - Optional value = service.findFirstValue(clazz, attrTarget, resolvedCriteria); + Optional value = objectCache.findFirstValue(clazz, attrTarget, resolvedCriteria); return value.orElse(null); } @@ -266,4 +269,8 @@ public void setFomXml(FOMXML fomXml) { public void setHLADecoderRegistry(HLADecoderRegistry hdr) { this.hlaDecoderRegistry = hdr; } + + ObjectCache objectCache() { + return objectCache; + } } diff --git a/src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java b/src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java new file mode 100644 index 0000000..654cf52 --- /dev/null +++ b/src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java @@ -0,0 +1,74 @@ +package com.yetanalytics.hlaxapi.cache; + +import com.yetanalytics.hlaxapi.FOMXML; +import com.yetanalytics.hlaxapi.HLADecoderRegistry; +import com.yetanalytics.hlaxapi.config.XapiConfig; +import com.yetanalytics.hlaxapi.config.model.Expression; +import com.yetanalytics.hlaxapi.config.model.Target; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class ObjectCache implements AutoCloseable { + + private final FomCatalog catalog; + private final Map> subscriptions; + private HlaObjectCache delegate; + + public ObjectCache(XapiConfig xapiConfig, FOMXML fomXml, HLADecoderRegistry decoderRegistry) { + this(xapiConfig, fomXml, decoderRegistry, HlaObjectCache.defaultJdbcUrl()); + } + + ObjectCache(XapiConfig xapiConfig, FOMXML fomXml, HLADecoderRegistry decoderRegistry, String jdbcUrl) { + this.catalog = FomCatalog.fromFomXml(fomXml); + this.subscriptions = QueryReferenceCollector.collect(xapiConfig.statementTriggers); + if (!subscriptions.isEmpty()) { + this.delegate = new HlaObjectCache(jdbcUrl, catalog, fomXml, decoderRegistry); + } + } + + public boolean isEnabled() { + return delegate != null; + } + + public Map> subscriptions() { + return subscriptions; + } + + public FomCatalog catalog() { + return catalog; + } + + public Optional findFirstValue(String clazz, Target attrTarget, Expression criteria) { + if (delegate == null) { + return Optional.empty(); + } + return delegate.queryService().findFirstValue(clazz, attrTarget, criteria); + } + + public void discoverObject(String objectHandle, String objectName, String className) { + if (delegate != null) { + delegate.discoverObject(objectHandle, objectName, className); + } + } + + public void reflectAttributeValue(String objectHandle, String className, String attributeName, byte[] bytes) { + if (delegate != null) { + delegate.reflectAttributeValue(objectHandle, className, attributeName, bytes); + } + } + + public void removeObject(String objectHandle) { + if (delegate != null) { + delegate.removeObject(objectHandle); + } + } + + @Override + public synchronized void close() { + if (delegate != null) { + delegate.close(); + delegate = null; + } + } +} diff --git a/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java b/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java new file mode 100644 index 0000000..0681a7e --- /dev/null +++ b/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java @@ -0,0 +1,58 @@ +package com.yetanalytics.hlaxapi; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import com.yetanalytics.hlaxapi.cache.ObjectCache; +import com.yetanalytics.hlaxapi.config.XapiConfig; +import hla.rti1516e.encoding.EncoderFactory; +import org.junit.jupiter.api.Test; +import org.portico.impl.hla1516e.types.encoding.HLA1516eEncoderFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +class ObjectCacheSpringContextTest { + + @Test + void springWiresObjectCacheIntoConsumers() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.register( + TestConfig.class, + FOMXML.class, + HlaInterfaceImpl.class, + InjectionHandler.class, + TriggerProcessor.class); + context.refresh(); + + ObjectCache objectCache = context.getBean(ObjectCache.class); + InjectionHandler injectionHandler = context.getBean(InjectionHandler.class); + HlaInterfaceImpl hlaInterface = context.getBean(HlaInterfaceImpl.class); + + assertNotNull(hlaInterface); + assertSame(objectCache, injectionHandler.objectCache()); + } + } + + @Configuration + static class TestConfig extends AppConfig { + + @Override + @Bean + public EncoderFactory encoderFactory() { + return new HLA1516eEncoderFactory(); + } + + @Override + @Bean + public XapiConfig xapiConfig() { + return new XapiConfig(); + } + + @Override + @Bean + public SimulationConfig simulationConfig() { + return new SimulationConfig(null, null, null, null, "config/HlaFedereplFOM.xml"); + } + } +} diff --git a/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java b/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java new file mode 100644 index 0000000..551b5aa --- /dev/null +++ b/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java @@ -0,0 +1,103 @@ +package com.yetanalytics.hlaxapi.cache; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.yetanalytics.hlaxapi.FOMXML; +import com.yetanalytics.hlaxapi.HLADecoderRegistry; +import com.yetanalytics.hlaxapi.SimulationConfig; +import com.yetanalytics.hlaxapi.config.XapiConfig; +import com.yetanalytics.hlaxapi.config.model.ComparisonOperator; +import com.yetanalytics.hlaxapi.config.model.Criterion; +import com.yetanalytics.hlaxapi.config.model.StatementTrigger; +import com.yetanalytics.hlaxapi.config.model.Target; +import com.yetanalytics.hlaxapi.config.model.ValueExpression; +import hla.rti1516e.encoding.DataElement; +import hla.rti1516e.encoding.EncoderException; +import hla.rti1516e.encoding.EncoderFactory; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.portico.impl.hla1516e.types.encoding.HLA1516eEncoderFactory; + +class ObjectCacheTest { + + private final EncoderFactory encoderFactory = new HLA1516eEncoderFactory(); + private final HLADecoderRegistry decoderRegistry = new HLADecoderRegistry(encoderFactory); + private final FOMXML fomXml = new FOMXML( + new SimulationConfig(null, null, null, null, "config/HlaFedereplFOM.xml"), + decoderRegistry); + + @Test + void disabledWhenNoQueryInjectionsAndDoesNotOpenSqlite(@TempDir Path tempDir) { + Path databasePath = tempDir.resolve("disabled.sqlite"); + + try (ObjectCache cache = new ObjectCache( + new XapiConfig(), + fomXml, + decoderRegistry, + "jdbc:sqlite:" + databasePath)) { + cache.discoverObject("object-1", "Rabbit One", "Rabbit"); + cache.reflectAttributeValue("object-1", "Rabbit", "Hunger", encoded(encoderFactory.createHLAinteger32BE( + 75))); + cache.removeObject("object-1"); + + assertFalse(cache.isEnabled()); + assertTrue(cache.subscriptions().isEmpty()); + assertFalse(cache.findFirstValue("Rabbit", new Target(List.of("Hunger")), null).isPresent()); + assertFalse(Files.exists(databasePath)); + } + } + + @Test + void enabledWhenQueryInjectionsExistAndCanQueryReflectedValues(@TempDir Path tempDir) { + Path databasePath = tempDir.resolve("enabled.sqlite"); + + try (ObjectCache cache = new ObjectCache( + configWithQuery(), + fomXml, + decoderRegistry, + "jdbc:sqlite:" + databasePath)) { + cache.discoverObject("object-1", "Rabbit One", "Rabbit"); + cache.reflectAttributeValue("object-1", "Rabbit", "EntityId", encoded(encoderFactory + .createHLAASCIIstring("rabbit-one"))); + cache.reflectAttributeValue("object-1", "Rabbit", "Hunger", encoded(encoderFactory.createHLAinteger32BE( + 75))); + + Criterion criteria = new Criterion( + new Target(List.of("Hunger")), + ComparisonOperator.GT, + new ValueExpression(50)); + + assertTrue(cache.isEnabled()); + assertEquals(Set.of("EntityId", "Hunger"), cache.subscriptions().get("Rabbit")); + assertEquals( + "rabbit-one", + cache.findFirstValue("Rabbit", new Target(List.of("EntityId")), criteria).orElseThrow()); + assertTrue(Files.exists(databasePath)); + } + } + + private XapiConfig configWithQuery() { + StatementTrigger trigger = new StatementTrigger(); + trigger.statement = """ + {"actor":{"name":["query","Rabbit",["EntityId"],[["Hunger"],">",50]]}} + """; + + XapiConfig config = new XapiConfig(); + config.statementTriggers = List.of(trigger); + return config; + } + + private byte[] encoded(DataElement element) { + try { + return element.toByteArray(); + } catch (EncoderException e) { + throw new IllegalStateException("Could not encode test value", e); + } + } +} From 84cb59d9342afedc28ace91cab00f9ee7622e9a5 Mon Sep 17 00:00:00 2001 From: Milton Reder Date: Wed, 1 Jul 2026 16:09:27 -0400 Subject: [PATCH 2/5] remove one-arg constructor --- src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java b/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java index 9963233..f30bc4e 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java +++ b/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java @@ -47,11 +47,6 @@ public class InjectionHandler { public InjectionHandler() { } - @Autowired - public InjectionHandler(ObjectCache objectCache) { - this.objectCache = objectCache; - } - public Object handleThis(Target t, InjectionContext context) { if (context instanceof InteractionInjectionContext) { return handleThis(t, (InteractionInjectionContext) context); From dea23036c9019c45073b0cb441d26eb6752bd214 Mon Sep 17 00:00:00 2001 From: Milton Reder Date: Wed, 1 Jul 2026 16:10:11 -0400 Subject: [PATCH 3/5] FomCatalog spring bean --- .../java/com/yetanalytics/hlaxapi/AppConfig.java | 9 ++++++++- .../com/yetanalytics/hlaxapi/InjectionHandler.java | 1 + .../com/yetanalytics/hlaxapi/cache/ObjectCache.java | 13 +++++++++---- .../hlaxapi/ObjectCacheSpringContextTest.java | 3 +++ .../yetanalytics/hlaxapi/cache/ObjectCacheTest.java | 3 +++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java b/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java index dceb6d6..42ef24d 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java +++ b/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java @@ -11,6 +11,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import com.yetanalytics.hlaxapi.cache.FomCatalog; import com.yetanalytics.hlaxapi.cache.ObjectCache; import com.yetanalytics.hlaxapi.config.ConfigParser; import com.yetanalytics.hlaxapi.config.XapiConfig; @@ -41,12 +42,18 @@ public HLADecoderRegistry hlaDecoderRegistry(EncoderFactory encoderFactory) { return new HLADecoderRegistry(encoderFactory); } + @Bean + public FomCatalog fomCatalog(FOMXML fomXml) { + return FomCatalog.fromFomXml(fomXml); + } + @Bean(destroyMethod = "close") public ObjectCache objectCache( XapiConfig xapiConfig, + FomCatalog fomCatalog, FOMXML fomXml, HLADecoderRegistry decoderRegistry) { - return new ObjectCache(xapiConfig, fomXml, decoderRegistry); + return new ObjectCache(xapiConfig, fomCatalog, fomXml, decoderRegistry); } @Bean diff --git a/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java b/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java index f30bc4e..4273a7e 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java +++ b/src/main/java/com/yetanalytics/hlaxapi/InjectionHandler.java @@ -36,6 +36,7 @@ public class InjectionHandler { private static final Logger logger = LogManager.getLogger(InjectionHandler.class); + @Autowired private ObjectCache objectCache; @Autowired diff --git a/src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java b/src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java index 654cf52..008255b 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java +++ b/src/main/java/com/yetanalytics/hlaxapi/cache/ObjectCache.java @@ -15,12 +15,17 @@ public class ObjectCache implements AutoCloseable { private final Map> subscriptions; private HlaObjectCache delegate; - public ObjectCache(XapiConfig xapiConfig, FOMXML fomXml, HLADecoderRegistry decoderRegistry) { - this(xapiConfig, fomXml, decoderRegistry, HlaObjectCache.defaultJdbcUrl()); + public ObjectCache(XapiConfig xapiConfig, FomCatalog catalog, FOMXML fomXml, HLADecoderRegistry decoderRegistry) { + this(xapiConfig, catalog, fomXml, decoderRegistry, HlaObjectCache.defaultJdbcUrl()); } - ObjectCache(XapiConfig xapiConfig, FOMXML fomXml, HLADecoderRegistry decoderRegistry, String jdbcUrl) { - this.catalog = FomCatalog.fromFomXml(fomXml); + ObjectCache( + XapiConfig xapiConfig, + FomCatalog catalog, + FOMXML fomXml, + HLADecoderRegistry decoderRegistry, + String jdbcUrl) { + this.catalog = catalog; this.subscriptions = QueryReferenceCollector.collect(xapiConfig.statementTriggers); if (!subscriptions.isEmpty()) { this.delegate = new HlaObjectCache(jdbcUrl, catalog, fomXml, decoderRegistry); diff --git a/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java b/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java index 0681a7e..09c6fd5 100644 --- a/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java +++ b/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; +import com.yetanalytics.hlaxapi.cache.FomCatalog; import com.yetanalytics.hlaxapi.cache.ObjectCache; import com.yetanalytics.hlaxapi.config.XapiConfig; import hla.rti1516e.encoding.EncoderFactory; @@ -25,11 +26,13 @@ void springWiresObjectCacheIntoConsumers() { TriggerProcessor.class); context.refresh(); + FomCatalog fomCatalog = context.getBean(FomCatalog.class); ObjectCache objectCache = context.getBean(ObjectCache.class); InjectionHandler injectionHandler = context.getBean(InjectionHandler.class); HlaInterfaceImpl hlaInterface = context.getBean(HlaInterfaceImpl.class); assertNotNull(hlaInterface); + assertSame(fomCatalog, objectCache.catalog()); assertSame(objectCache, injectionHandler.objectCache()); } } diff --git a/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java b/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java index 551b5aa..07592b0 100644 --- a/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java +++ b/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java @@ -31,6 +31,7 @@ class ObjectCacheTest { private final FOMXML fomXml = new FOMXML( new SimulationConfig(null, null, null, null, "config/HlaFedereplFOM.xml"), decoderRegistry); + private final FomCatalog catalog = FomCatalog.fromFomXml(fomXml); @Test void disabledWhenNoQueryInjectionsAndDoesNotOpenSqlite(@TempDir Path tempDir) { @@ -38,6 +39,7 @@ void disabledWhenNoQueryInjectionsAndDoesNotOpenSqlite(@TempDir Path tempDir) { try (ObjectCache cache = new ObjectCache( new XapiConfig(), + catalog, fomXml, decoderRegistry, "jdbc:sqlite:" + databasePath)) { @@ -59,6 +61,7 @@ void enabledWhenQueryInjectionsExistAndCanQueryReflectedValues(@TempDir Path tem try (ObjectCache cache = new ObjectCache( configWithQuery(), + catalog, fomXml, decoderRegistry, "jdbc:sqlite:" + databasePath)) { From fef0683c44f56151355f6981de69bd7d13af605b Mon Sep 17 00:00:00 2001 From: Milton Reder Date: Wed, 1 Jul 2026 16:51:48 -0400 Subject: [PATCH 4/5] one-arg constructor for fomcatalog and make it a component --- .../com/yetanalytics/hlaxapi/AppConfig.java | 5 ---- .../hlaxapi/cache/FomCatalog.java | 29 +++++++++---------- .../hlaxapi/cache/FomCatalogTest.java | 2 +- .../hlaxapi/cache/HlaObjectCacheTest.java | 2 +- .../hlaxapi/cache/ObjectCacheTest.java | 2 +- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java b/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java index 42ef24d..634002f 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java +++ b/src/main/java/com/yetanalytics/hlaxapi/AppConfig.java @@ -42,11 +42,6 @@ public HLADecoderRegistry hlaDecoderRegistry(EncoderFactory encoderFactory) { return new HLADecoderRegistry(encoderFactory); } - @Bean - public FomCatalog fomCatalog(FOMXML fomXml) { - return FomCatalog.fromFomXml(fomXml); - } - @Bean(destroyMethod = "close") public ObjectCache objectCache( XapiConfig xapiConfig, diff --git a/src/main/java/com/yetanalytics/hlaxapi/cache/FomCatalog.java b/src/main/java/com/yetanalytics/hlaxapi/cache/FomCatalog.java index 9e198ec..829c26e 100644 --- a/src/main/java/com/yetanalytics/hlaxapi/cache/FomCatalog.java +++ b/src/main/java/com/yetanalytics/hlaxapi/cache/FomCatalog.java @@ -12,6 +12,8 @@ import java.util.Optional; import java.util.Set; import javax.xml.xpath.XPathExpressionException; + +import org.springframework.stereotype.Component; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -20,14 +22,24 @@ /** * FOM-derived object metadata used by the SQLite cache. */ +@Component public final class FomCatalog { private final Map classesByName; private final Map classesById; private final Map attributesById; - private FomCatalog(Map classesByName) { - this.classesByName = Collections.unmodifiableMap(classesByName); + public FomCatalog(FOMXML fomXml) { + Objects.requireNonNull(fomXml, "fomXml"); + CatalogBuilder builder = new CatalogBuilder(fomXml); + Document document = fomXml.getDoc(); + Element objects = firstElement(document.getDocumentElement(), "objects"); + if (objects != null) { + for (Element objectClass : childElements(objects, "objectClass")) { + builder.parseObjectClass(objectClass, null, new ArrayList<>()); + } + } + this.classesByName = builder.classesByName; Map byId = new LinkedHashMap<>(); Map attrsById = new LinkedHashMap<>(); @@ -41,19 +53,6 @@ private FomCatalog(Map classesByName) { this.attributesById = Collections.unmodifiableMap(attrsById); } - public static FomCatalog fromFomXml(FOMXML fomXml) { - Objects.requireNonNull(fomXml, "fomXml"); - CatalogBuilder builder = new CatalogBuilder(fomXml); - Document document = fomXml.getDoc(); - Element objects = firstElement(document.getDocumentElement(), "objects"); - if (objects != null) { - for (Element objectClass : childElements(objects, "objectClass")) { - builder.parseObjectClass(objectClass, null, new ArrayList<>()); - } - } - return new FomCatalog(builder.classesByName); - } - public Collection objectClasses() { return classesByName.values(); } diff --git a/src/test/java/com/yetanalytics/hlaxapi/cache/FomCatalogTest.java b/src/test/java/com/yetanalytics/hlaxapi/cache/FomCatalogTest.java index 18da6e7..b70a837 100644 --- a/src/test/java/com/yetanalytics/hlaxapi/cache/FomCatalogTest.java +++ b/src/test/java/com/yetanalytics/hlaxapi/cache/FomCatalogTest.java @@ -53,6 +53,6 @@ void convertsTargetsToPathKeys() { private FomCatalog catalog(String fomPath) { SimulationConfig simConfig = new SimulationConfig(null, null, null, null, fomPath); HLADecoderRegistry decoderRegistry = new HLADecoderRegistry(new HLA1516eEncoderFactory()); - return FomCatalog.fromFomXml(new FOMXML(simConfig, decoderRegistry)); + return new FomCatalog(new FOMXML(simConfig, decoderRegistry)); } } diff --git a/src/test/java/com/yetanalytics/hlaxapi/cache/HlaObjectCacheTest.java b/src/test/java/com/yetanalytics/hlaxapi/cache/HlaObjectCacheTest.java index 55a7d38..13c4309 100644 --- a/src/test/java/com/yetanalytics/hlaxapi/cache/HlaObjectCacheTest.java +++ b/src/test/java/com/yetanalytics/hlaxapi/cache/HlaObjectCacheTest.java @@ -34,7 +34,7 @@ class HlaObjectCacheTest { private final FOMXML fomXml = new FOMXML( new SimulationConfig(null, null, null, null, "config/HlaFedereplFOM.xml"), decoderRegistry); - private final FomCatalog catalog = FomCatalog.fromFomXml(fomXml); + private final FomCatalog catalog = new FomCatalog(fomXml); @Test void initializesSchemaAndSeedsFomMetadata() throws SQLException { diff --git a/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java b/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java index 07592b0..db00da2 100644 --- a/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java +++ b/src/test/java/com/yetanalytics/hlaxapi/cache/ObjectCacheTest.java @@ -31,7 +31,7 @@ class ObjectCacheTest { private final FOMXML fomXml = new FOMXML( new SimulationConfig(null, null, null, null, "config/HlaFedereplFOM.xml"), decoderRegistry); - private final FomCatalog catalog = FomCatalog.fromFomXml(fomXml); + private final FomCatalog catalog = new FomCatalog(fomXml); @Test void disabledWhenNoQueryInjectionsAndDoesNotOpenSqlite(@TempDir Path tempDir) { From 01198f0f2b25d41f25cb59343065871bf793b9af Mon Sep 17 00:00:00 2001 From: Milton Reder Date: Wed, 1 Jul 2026 16:58:20 -0400 Subject: [PATCH 5/5] update silly test --- .../com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java b/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java index 09c6fd5..a5762bb 100644 --- a/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java +++ b/src/test/java/com/yetanalytics/hlaxapi/ObjectCacheSpringContextTest.java @@ -21,6 +21,7 @@ void springWiresObjectCacheIntoConsumers() { context.register( TestConfig.class, FOMXML.class, + FomCatalog.class, HlaInterfaceImpl.class, InjectionHandler.class, TriggerProcessor.class);