From 24abd6bd65a974bc055f292db21826fd2bfb788b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 28 Dec 2024 17:08:41 -0500 Subject: [PATCH] Startup and Testpage Overlay tweaks (#6578) * Startup and Testpage Overlay tweaks * Add changelog * Account for review comments * Test fix --- .editorconfig | 5 + .../6578-adjust-testpage-for-bootstrap-5.yaml | 6 + .../6578-avoid-database-flood-on-startup.yaml | 6 + .../GraphQLProviderWithIntrospection.java | 40 ++-- .../jpa/search/builder/SearchBuilder.java | 1 + .../jpa/topic/SubscriptionTopicLoader.java | 3 + .../cache/BaseResourceCacheSynchronizer.java | 25 ++- .../ca/uhn/fhir/to/FhirTesterMvcConfig.java | 3 +- .../webapp/WEB-INF/templates/resource.html | 103 +++++----- .../WEB-INF/templates/tmpl-navbar-left.html | 40 ++-- .../src/main/webapp/css/tester.css | 45 +---- .../src/main/webapp/js/RestfulTester.js | 180 ++++++++---------- .../support/ValidationSupportChain.java | 17 +- 13 files changed, 229 insertions(+), 245 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-adjust-testpage-for-bootstrap-5.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-avoid-database-flood-on-startup.yaml diff --git a/.editorconfig b/.editorconfig index c42d0f67472..103bbd75f12 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,6 +23,11 @@ indent_style = tab tab_width = 3 indent_size = 3 +[*.js] +indent_style = tab +tab_width = 3 +indent_size = 3 + [*.vm] indent_style = tab tab_width = 3 diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-adjust-testpage-for-bootstrap-5.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-adjust-testpage-for-bootstrap-5.yaml new file mode 100644 index 00000000000..210ab2dd5e9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-adjust-testpage-for-bootstrap-5.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 6578 +title: "The search parameter picker in the Testpage Overlay module has been adjusted to + correct several display issues which appeared after the upgrade from Bootstrap 4 to + Bootstrap 5." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-avoid-database-flood-on-startup.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-avoid-database-flood-on-startup.yaml new file mode 100644 index 00000000000..a6af6ad1962 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_8_0/6578-avoid-database-flood-on-startup.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 6578 +title: "When starting up the JPA server under heavy load, the validation support cache + could perform a large number of identical parallel queries. A synchronization guard + has been placed around the cache loader to avoid this." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/GraphQLProviderWithIntrospection.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/GraphQLProviderWithIntrospection.java index cb34cc222ff..4c027ec307d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/GraphQLProviderWithIntrospection.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/graphql/GraphQLProviderWithIntrospection.java @@ -31,6 +31,8 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.util.StringUtil; import ca.uhn.fhir.util.VersionUtil; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -78,7 +80,7 @@ import static ca.uhn.fhir.util.MessageSupplier.msg; public class GraphQLProviderWithIntrospection extends GraphQLProvider { private static final Logger ourLog = LoggerFactory.getLogger(GraphQLProviderWithIntrospection.class); - private final GraphQLSchemaGenerator myGenerator; + private final Supplier myGenerator; private final ISearchParamRegistry mySearchParamRegistry; private final VersionSpecificWorkerContextWrapper myContext; private final IDaoRegistry myDaoRegistry; @@ -99,12 +101,16 @@ public class GraphQLProviderWithIntrospection extends GraphQLProvider { myDaoRegistry = theDaoRegistry; myContext = VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(theValidationSupport); - myGenerator = new GraphQLSchemaGenerator(myContext, VersionUtil.getVersion()); GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Collections.emptyList().getClass(), (JsonSerializer) (src, typeOfSrc, context) -> new JsonArray()); myGson = gsonBuilder.create(); + + // Lazy-load this because it's expensive, but more importantly because it makes a bunch of + // calls for StructureDefinitions and other such things during startup so we want to be sure + // that everything else is initialized first + myGenerator = Suppliers.memoize(() -> new GraphQLSchemaGenerator(myContext, VersionUtil.getVersion())); } @Override @@ -153,37 +159,19 @@ public class GraphQLProviderWithIntrospection extends GraphQLProvider { Collection theResourceTypes, EnumSet theOperations) { + GraphQLSchemaGenerator generator = myGenerator.get(); + final StringBuilder schemaBuilder = new StringBuilder(); try (Writer writer = new StringBuilderWriter(schemaBuilder)) { // Generate FHIR base types schemas - myGenerator.generateTypes(writer, theOperations); + generator.generateTypes(writer, theOperations); // Fix up a few things that are missing from the generated schema writer.append("\ninterface Element {") .append("\n id: ID") .append("\n}") .append("\n"); - // writer - // .append("\ninterface Quantity {\n") - // .append("id: String\n") - // .append("extension: [Extension]\n") - // .append("value: decimal _value: ElementBase\n") - // .append("comparator: code _comparator: ElementBase\n") - // .append("unit: String _unit: ElementBase\n") - // .append("system: uri _system: ElementBase\n") - // .append("code: code _code: ElementBase\n") - // .append("\n}") - // .append("\n"); - - // writer - // .append("\ntype Resource {") - // .append("\n id: [token]" + "\n}") - // .append("\n"); - // writer - // .append("\ninput ResourceInput {") - // .append("\n id: [token]" + "\n}") - // .append("\n"); // Generate schemas for the resource types for (String nextResourceType : theResourceTypes) { @@ -192,7 +180,7 @@ public class GraphQLProviderWithIntrospection extends GraphQLProvider { .getActiveSearchParams( nextResourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH) .values()); - myGenerator.generateResource(writer, sd, parameters, theOperations); + generator.generateResource(writer, sd, parameters, theOperations); } // Generate queries @@ -210,8 +198,8 @@ public class GraphQLProviderWithIntrospection extends GraphQLProvider { .getActiveSearchParams( nextResourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH) .values()); - myGenerator.generateListAccessQuery(writer, parameters, nextResourceType); - myGenerator.generateConnectionAccessQuery(writer, parameters, nextResourceType); + generator.generateListAccessQuery(writer, parameters, nextResourceType); + generator.generateConnectionAccessQuery(writer, parameters, nextResourceType); } } writer.append("\n}"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index 857ef6e134c..a6712b27b6b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -285,6 +285,7 @@ public class SearchBuilder implements ISearchBuilder { mySearchProperties.setMaxResultsRequested(theMaxResultsToFetch); } + @Override public void setDeduplicateInDatabase(boolean theShouldDeduplicateInDB) { mySearchProperties.setDeduplicateInDatabase(theShouldDeduplicateInDB); } diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/topic/SubscriptionTopicLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/topic/SubscriptionTopicLoader.java index fe81226df6c..7bc86cb0fe4 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/topic/SubscriptionTopicLoader.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/topic/SubscriptionTopicLoader.java @@ -34,6 +34,8 @@ import org.hl7.fhir.r5.model.Enumerations; import org.hl7.fhir.r5.model.SubscriptionTopic; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; import java.util.HashSet; import java.util.List; @@ -59,6 +61,7 @@ public class SubscriptionTopicLoader extends BaseResourceCacheSynchronizer { } @Override + @EventListener(classes = ContextRefreshedEvent.class) public void registerListener() { if (!myFhirContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4B)) { return; diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/cache/BaseResourceCacheSynchronizer.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/cache/BaseResourceCacheSynchronizer.java index 26f064a3630..41e434e70c5 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/cache/BaseResourceCacheSynchronizer.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/cache/BaseResourceCacheSynchronizer.java @@ -19,6 +19,7 @@ */ package ca.uhn.fhir.cache; +import ca.uhn.fhir.IHapiBootOrder; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.cache.IResourceChangeEvent; @@ -30,7 +31,6 @@ import ca.uhn.fhir.jpa.searchparam.retry.Retrier; import ca.uhn.fhir.rest.api.server.SystemRequestDetails; import com.google.common.annotations.VisibleForTesting; import jakarta.annotation.Nonnull; -import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import org.apache.commons.lang3.time.DateUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -40,7 +40,9 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.ContextStartedEvent; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; import java.util.Collection; import java.util.List; @@ -78,21 +80,34 @@ public abstract class BaseResourceCacheSynchronizer implements IResourceChangeLi myResourceChangeListenerRegistry = theResourceChangeListenerRegistry; } - @PostConstruct + /** + * This method performs a search in the DB, so use the {@link ContextStartedEvent} + * to ensure that it runs after the database initializer + */ + @EventListener(classes = ContextRefreshedEvent.class) + @Order(IHapiBootOrder.AFTER_SUBSCRIPTION_INITIALIZED) public void registerListener() { if (myDaoRegistry.getResourceDaoOrNull(myResourceName) == null) { ourLog.info("No resource DAO found for resource type {}, not registering listener", myResourceName); return; } - mySearchParameterMap = getSearchParameterMap(); mySystemRequestDetails = SystemRequestDetails.forAllPartitions(); IResourceChangeListenerCache resourceCache = myResourceChangeListenerRegistry.registerResourceResourceChangeListener( - myResourceName, mySearchParameterMap, this, REFRESH_INTERVAL); + myResourceName, provideSearchParameterMap(), this, REFRESH_INTERVAL); resourceCache.forceRefresh(); } + private SearchParameterMap provideSearchParameterMap() { + SearchParameterMap searchParameterMap = mySearchParameterMap; + if (searchParameterMap == null) { + searchParameterMap = getSearchParameterMap(); + mySearchParameterMap = searchParameterMap; + } + return searchParameterMap; + } + @PreDestroy public void unregisterListener() { myResourceChangeListenerRegistry.unregisterResourceResourceChangeListener(this); @@ -149,7 +164,7 @@ public abstract class BaseResourceCacheSynchronizer implements IResourceChangeLi ourLog.debug("Starting sync {}s", myResourceName); List resourceList = (List) - getResourceDao().searchForResources(mySearchParameterMap, mySystemRequestDetails); + getResourceDao().searchForResources(provideSearchParameterMap(), mySystemRequestDetails); return syncResourcesIntoCache(resourceList); } } diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java index 9c99649404b..7421a5898a6 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.to; +import ca.uhn.fhir.system.HapiSystemProperties; import ca.uhn.fhir.to.mvc.AnnotationMethodHandlerAdapterConfigurer; import ca.uhn.fhir.to.util.WebUtil; import jakarta.annotation.Nonnull; @@ -49,7 +50,7 @@ public class FhirTesterMvcConfig implements WebMvcConfigurer { resolver.setTemplateMode(TemplateMode.HTML); resolver.setCharacterEncoding("UTF-8"); - if (theTesterConfig.getDebugTemplatesMode()) { + if (theTesterConfig.getDebugTemplatesMode() || HapiSystemProperties.isUnitTestModeEnabled()) { resolver.setCacheable(false); } diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html index 7e41af3dbb7..feabac19450 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/resource.html @@ -134,69 +134,72 @@

Includes Also include resources which are referenced by the search results

- - - +
+ + + + + - - +
-

Sort Results

-
- -
- - - - -
+
+
+
+ + + + + +
+
- -
- - - - -
+
+
+ + + + + +
+
-
-
-

Other Options

+
+

Other Options

+
-
- Limit -
+
@@ -209,15 +212,17 @@

Reverse Includes Also include resources which reference to the search results

- - - - - + - + + + + + + + + -
diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-left.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-left.html index 5697544951d..f62e74a1ce9 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-left.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-left.html @@ -9,46 +9,46 @@
- + - + - +

- + - + - +

- + - + - + - + - +