From 06e916b9e5021621bbe90122bc2f54db36409f49 Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Tue, 24 Jan 2023 10:01:32 +0100 Subject: [PATCH 1/8] Closes #3329. This implements a new optional `cacheable` parameter for these REST endpoints: - `/rest/items` - `/rest/things` - `/rest/rules` When this parameter is set, a flat list of all elements excluding non-cacheable fields (e.g. "state", "transformedState", "stateDescription", "commandDescription" for items, "statusInfo", "firmwareStatus", "properties" for things, "status" for rules) will be retrieved along with a `Last-Modified` HTTP response header. When unknown, the Last-Modified header will be set to the date of the request. Also only when this parameter is set, and a `If-Modified-Since` header is found in the request, that header will be compared to the last known modified date for the corresponding cacheable list. The last modified date will be reset when any change is made on the elements of the underlying registry. If the `If-Modified-Since` date is equal or more recent than the last modified date, then a 304 Not Modified response with no content will be served instead of the usual 200 OK, informing the client that its cache is still valid at the provided date. All other request parameters will be ignored except for "metadata" in the `/rest/items` endpoint. When a metadata selector is set, the resulting item list will be considered like a completely different resource, i.e. it will have its own last modified date. Regarding metadata, the approach to invalidating last modified dates is very conservative: when any metadata is changed, all cacheable lists of items will have their last modified date reset even if the change was in a metadata namespace that wasn't requested. This also implements the abovedescribed behavior for the `/rest/ui/components/{namespace}` endpoint, but no `cacheable` parameter is necessary. The last modified date is tracked by namespace. Signed-off-by: Yannick Schaus --- .../rest/internal/RuleResource.java | 61 ++++++++++++- .../rest/core/internal/item/ItemResource.java | 91 ++++++++++++++++++- .../core/internal/thing/ThingResource.java | 58 +++++++++++- .../core/io/rest/ui/internal/UIResource.java | 72 ++++++++++++++- 4 files changed, 275 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index 04e5e2155d6..9d62ccb93cc 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -17,9 +17,11 @@ import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.function.Predicate; @@ -29,6 +31,7 @@ import javax.annotation.security.RolesAllowed; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; @@ -38,6 +41,7 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; @@ -67,6 +71,7 @@ import org.openhab.core.automation.rest.internal.dto.EnrichedRuleDTOMapper; import org.openhab.core.automation.util.ModuleBuilder; import org.openhab.core.automation.util.RuleBuilder; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.config.core.ConfigUtil; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.rest.DTOMapper; @@ -77,6 +82,7 @@ import org.openhab.core.library.types.DateTimeType; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -125,8 +131,10 @@ public class RuleResource implements RESTResource { private final RuleManager ruleManager; private final RuleRegistry ruleRegistry; private final ManagedRuleProvider managedRuleProvider; + private final ResetLastModifiedChangeListener resetLastModifiedChangeListener = new ResetLastModifiedChangeListener(); private @Context @NonNullByDefault({}) UriInfo uriInfo; + private @Nullable Date cacheableListLastModified = null; @Activate public RuleResource( // @@ -138,15 +146,42 @@ public RuleResource( // this.ruleManager = ruleManager; this.ruleRegistry = ruleRegistry; this.managedRuleProvider = managedRuleProvider; + + this.ruleRegistry.addRegistryChangeListener(resetLastModifiedChangeListener); + } + + @Deactivate + void deactivate() { + this.ruleRegistry.removeRegistryChangeListener(resetLastModifiedChangeListener); } @GET @Produces(MediaType.APPLICATION_JSON) @Operation(operationId = "getRules", summary = "Get available rules, optionally filtered by tags and/or prefix.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedRuleDTO.class)))) }) - public Response get(@QueryParam("prefix") final @Nullable String prefix, + public Response get(@Context Request request, @QueryParam("prefix") final @Nullable String prefix, @QueryParam("tags") final @Nullable List tags, - @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary) { + @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, + @DefaultValue("false") @QueryParam("cacheable") @Parameter(description = "provides a cacheable list and honors the If-Modified-Since header, all other parameters are ignored") boolean cacheable) { + + if (cacheable) { + if (cacheableListLastModified != null) { + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + cacheableListLastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + } + + Stream rules = ruleRegistry.stream() + .map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)); + + rules = dtoMapper.limitToFields(rules, "uid,templateUID,name,visibility,description,tags,editable"); + return Response.ok(new Stream2JSONInputStream(rules)).lastModified(cacheableListLastModified).build(); + } + // match all Predicate p = r -> true; @@ -552,4 +587,26 @@ public Response setModuleConfigParam(@PathParam("ruleUID") @Parameter(descriptio return null; } } + + private void resetCacheableListLastModified() { + cacheableListLastModified = null; + } + + private class ResetLastModifiedChangeListener implements RegistryChangeListener { + + @Override + public void added(Rule element) { + resetCacheableListLastModified(); + } + + @Override + public void removed(Rule element) { + resetCacheableListLastModified(); + } + + @Override + public void updated(Rule oldElement, Rule element) { + resetCacheableListLastModified(); + } + } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 21d69b05886..dd543a2d649 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -12,9 +12,12 @@ */ package org.openhab.core.io.rest.core.internal.item; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -43,6 +46,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; @@ -52,6 +56,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Role; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.events.EventPublisher; import org.openhab.core.io.rest.DTOMapper; import org.openhab.core.io.rest.JSONResponse; @@ -68,6 +73,7 @@ import org.openhab.core.items.ItemBuilderFactory; import org.openhab.core.items.ItemNotFoundException; import org.openhab.core.items.ItemRegistry; +import org.openhab.core.items.ItemRegistryChangeListener; import org.openhab.core.items.ManagedItemProvider; import org.openhab.core.items.Metadata; import org.openhab.core.items.MetadataKey; @@ -88,6 +94,7 @@ import org.openhab.core.types.TypeParser; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -174,6 +181,10 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context private final ManagedItemProvider managedItemProvider; private final MetadataRegistry metadataRegistry; private final MetadataSelectorMatcher metadataSelectorMatcher; + private final ItemRegistryChangeListener resetLastModifiedItemChangeListener = new ResetLastModifiedItemChangeListener(); + private final RegistryChangeListener resetLastModifiedMetadataChangeListener = new ResetLastModifiedMetadataChangeListener(); + + private Map<@Nullable String, Date> cacheableListsLastModified = new HashMap<>(); @Activate public ItemResource(// @@ -193,6 +204,15 @@ public ItemResource(// this.managedItemProvider = managedItemProvider; this.metadataRegistry = metadataRegistry; this.metadataSelectorMatcher = metadataSelectorMatcher; + + this.itemRegistry.addRegistryChangeListener(resetLastModifiedItemChangeListener); + this.metadataRegistry.addRegistryChangeListener(resetLastModifiedMetadataChangeListener); + } + + @Deactivate + void deactivate() { + this.itemRegistry.removeRegistryChangeListener(resetLastModifiedItemChangeListener); + this.metadataRegistry.removeRegistryChangeListener(resetLastModifiedMetadataChangeListener); } private UriBuilder uriBuilder(final UriInfo uriInfo, final HttpHeaders httpHeaders) { @@ -207,17 +227,42 @@ private UriBuilder uriBuilder(final UriInfo uriInfo, final HttpHeaders httpHeade @Operation(operationId = "getItems", summary = "Get all available items.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedItemDTO.class)))) }) public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders, + @Context Request request, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, @QueryParam("type") @Parameter(description = "item type filter") @Nullable String type, @QueryParam("tags") @Parameter(description = "item tag filter") @Nullable String tags, @QueryParam("metadata") @Parameter(description = "metadata selector - a comma separated list or a regular expression (suppressed if no value given)") @Nullable String namespaceSelector, @DefaultValue("false") @QueryParam("recursive") @Parameter(description = "get member items recursively") boolean recursive, - @QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields) { + @QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields, + @DefaultValue("false") @QueryParam("cacheable") @Parameter(description = "provides a cacheable list and checks the If-Modified-Since header, all other parameters are ignored except \"metadata\"") boolean cacheable) { final Locale locale = localeService.getLocale(language); final Set namespaces = splitAndFilterNamespaces(namespaceSelector, locale); final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders); + if (cacheable) { + Date lastModifiedDate = Date.from(Instant.now()); + if (cacheableListsLastModified.containsKey(namespaceSelector)) { + lastModifiedDate = cacheableListsLastModified.get(namespaceSelector); + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModifiedDate); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + lastModifiedDate = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + cacheableListsLastModified.put(namespaceSelector, lastModifiedDate); + } + + Stream itemStream = getItems(null, null).stream() // + .map(item -> EnrichedItemDTOMapper.map(item, false, null, uriBuilder, locale)) // + .peek(dto -> addMetadata(dto, namespaces, null)) // + .peek(dto -> dto.editable = isEditable(dto.name)); + itemStream = dtoMapper.limitToFields(itemStream, + "name,label,type,groupType,function,category,editable,groupNames,link,tags,metadata"); + return Response.ok(new Stream2JSONInputStream(itemStream)).lastModified(lastModifiedDate).build(); + } + Stream itemStream = getItems(type, tags).stream() // .map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale)) // .peek(dto -> addMetadata(dto, namespaces, null)) // @@ -935,4 +980,48 @@ private void addMetadata(EnrichedItemDTO dto, Set namespaces, @Nullable private boolean isEditable(String itemName) { return managedItemProvider.get(itemName) != null; } + + private void resetCacheableListsLastModified() { + this.cacheableListsLastModified.clear(); + } + + private class ResetLastModifiedItemChangeListener implements ItemRegistryChangeListener { + @Override + public void added(Item element) { + resetCacheableListsLastModified(); + } + + @Override + public void allItemsChanged(Collection oldItemNames) { + resetCacheableListsLastModified(); + } + + @Override + public void removed(Item element) { + resetCacheableListsLastModified(); + } + + @Override + public void updated(Item oldElement, Item element) { + resetCacheableListsLastModified(); + } + } + + private class ResetLastModifiedMetadataChangeListener implements RegistryChangeListener { + + @Override + public void added(Metadata element) { + resetCacheableListsLastModified(); + } + + @Override + public void removed(Metadata element) { + resetCacheableListsLastModified(); + } + + @Override + public void updated(Metadata oldElement, Metadata element) { + resetCacheableListsLastModified(); + } + } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java index ea86bb57340..61191e48fb8 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java @@ -15,9 +15,12 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -42,6 +45,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; @@ -49,6 +53,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Role; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.config.core.ConfigDescription; import org.openhab.core.config.core.ConfigDescriptionRegistry; import org.openhab.core.config.core.ConfigUtil; @@ -100,6 +105,7 @@ import org.openhab.core.thing.util.ThingHelper; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -164,8 +170,10 @@ public class ThingResource implements RESTResource { private final ThingRegistry thingRegistry; private final ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService; private final ThingTypeRegistry thingTypeRegistry; + private final ResetLastModifiedChangeListener resetLastModifiedChangeListener = new ResetLastModifiedChangeListener(); private @Context @NonNullByDefault({}) UriInfo uriInfo; + private @Nullable Date cacheableListLastModified = null; @Activate public ThingResource( // @@ -198,6 +206,13 @@ public ThingResource( // this.thingRegistry = thingRegistry; this.thingStatusInfoI18nLocalizationService = thingStatusInfoI18nLocalizationService; this.thingTypeRegistry = thingTypeRegistry; + + this.thingRegistry.addRegistryChangeListener(resetLastModifiedChangeListener); + } + + @Deactivate + void deactivate() { + this.thingRegistry.removeRegistryChangeListener(resetLastModifiedChangeListener); } /** @@ -291,13 +306,30 @@ public Response create( @Operation(operationId = "getThings", summary = "Get all available things.", security = { @SecurityRequirement(name = "oauth2", scopes = { "admin" }) }, responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedThingDTO.class), uniqueItems = true))) }) - public Response getAll( + public Response getAll(@Context Request request, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, - @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary) { + @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, + @DefaultValue("false") @QueryParam("cacheable") @Parameter(description = "provides a cacheable list and checks the If-Modified-Since header") boolean cacheable) { final Locale locale = localeService.getLocale(language); Stream thingStream = thingRegistry.stream().map(t -> convertToEnrichedThingDTO(t, locale)) .distinct(); + + if (cacheable) { + if (cacheableListLastModified != null) { + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + cacheableListLastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + } + + thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,location,editable"); + return Response.ok(new Stream2JSONInputStream(thingStream)).lastModified(cacheableListLastModified).build(); + } + if (summary != null && summary) { thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,statusInfo,firmwareStatus,location,editable"); @@ -853,4 +885,26 @@ private URI getConfigDescriptionURI(ChannelUID channelUID) { throw new BadRequestException("Invalid URI syntax: " + uriString); } } + + private void resetCacheableListLastModified() { + cacheableListLastModified = null; + } + + private class ResetLastModifiedChangeListener implements RegistryChangeListener { + + @Override + public void added(Thing element) { + resetCacheableListLastModified(); + } + + @Override + public void removed(Thing element) { + resetCacheableListLastModified(); + } + + @Override + public void updated(Thing oldElement, Thing element) { + resetCacheableListLastModified(); + } + } } diff --git a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java index 166a3863dd5..210a499b887 100644 --- a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java +++ b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java @@ -13,7 +13,11 @@ package org.openhab.core.io.rest.ui.internal; import java.security.InvalidParameterException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -27,13 +31,16 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.auth.Role; +import org.openhab.core.common.registry.RegistryChangeListener; import org.openhab.core.io.rest.RESTConstants; import org.openhab.core.io.rest.RESTResource; import org.openhab.core.io.rest.Stream2JSONInputStream; @@ -45,6 +52,7 @@ import org.openhab.core.ui.tiles.TileProvider; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants; import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired; @@ -84,6 +92,9 @@ public class UIResource implements RESTResource { private final UIComponentRegistryFactory componentRegistryFactory; private final TileProvider tileProvider; + private Map lastModifiedDates = new HashMap<>(); + private Map> registryChangeListeners = new HashMap<>(); + @Activate public UIResource( // final @Reference UIComponentRegistryFactory componentRegistryFactory, @@ -92,6 +103,14 @@ public UIResource( // this.tileProvider = tileProvider; } + @Deactivate + public void deactivate() { + registryChangeListeners.forEach((n, l) -> { + UIComponentRegistry registry = componentRegistryFactory.getRegistry(n); + registry.removeRegistryChangeListener(l); + }); + } + @GET @Path("/tiles") @Produces({ MediaType.APPLICATION_JSON }) @@ -107,7 +126,7 @@ public Response getAll() { @Produces({ MediaType.APPLICATION_JSON }) @Operation(operationId = "getRegisteredUIComponentsInNamespace", summary = "Get all registered UI components in the specified namespace.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = RootUIComponent.class)))) }) - public Response getAllComponents(@PathParam("namespace") String namespace, + public Response getAllComponents(@Context Request request, @PathParam("namespace") String namespace, @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary) { UIComponentRegistry registry = componentRegistryFactory.getRegistry(namespace); Stream components = registry.getAll().stream(); @@ -126,8 +145,29 @@ public Response getAllComponents(@PathParam("namespace") String namespace, } return component; }); + return Response.ok(new Stream2JSONInputStream(components)).build(); + } else { + if (!registryChangeListeners.containsKey(namespace)) { + RegistryChangeListener changeListener = new ResetLastModifiedChangeListener(namespace); + registryChangeListeners.put(namespace, changeListener); + registry.addRegistryChangeListener(changeListener); + } + + Date lastModifiedDate = Date.from(Instant.now()); + if (lastModifiedDates.containsKey(namespace)) { + lastModifiedDate = lastModifiedDates.get(namespace); + Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModifiedDate); + if (responseBuilder != null) { + // send 304 Not Modified + return responseBuilder.build(); + } + } else { + lastModifiedDate = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); + lastModifiedDates.put(namespace, lastModifiedDate); + } + + return Response.ok(new Stream2JSONInputStream(components)).lastModified(lastModifiedDate).build(); } - return Response.ok(new Stream2JSONInputStream(components)).build(); } @GET @@ -208,4 +248,32 @@ public Response deleteComponent(@PathParam("namespace") String namespace, private TileDTO toTileDTO(Tile tile) { return new TileDTO(tile.getName(), tile.getUrl(), tile.getOverlay(), tile.getImageUrl()); } + + private void resetLastModifiedDate(String namespace) { + lastModifiedDates.remove(namespace); + } + + private class ResetLastModifiedChangeListener implements RegistryChangeListener { + + private String namespace; + + ResetLastModifiedChangeListener(String namespace) { + this.namespace = namespace; + } + + @Override + public void added(RootUIComponent element) { + resetLastModifiedDate(namespace); + } + + @Override + public void removed(RootUIComponent element) { + resetLastModifiedDate(namespace); + } + + @Override + public void updated(RootUIComponent oldElement, RootUIComponent element) { + resetLastModifiedDate(namespace); + } + } } From 19fc6897ef25ee7236c673c9d1b0ef9c05d98fe0 Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Tue, 24 Jan 2023 11:01:01 +0100 Subject: [PATCH 2/8] Add cache control directives Signed-off-by: Yannick Schaus --- .../core/automation/rest/internal/RuleResource.java | 8 +++++++- .../core/io/rest/core/internal/item/ItemResource.java | 9 ++++++++- .../core/io/rest/core/internal/thing/ThingResource.java | 8 +++++++- .../org/openhab/core/io/rest/ui/internal/UIResource.java | 7 ++++++- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index 9d62ccb93cc..cbd8eb46657 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -39,6 +39,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; @@ -178,8 +179,13 @@ public Response get(@Context Request request, @QueryParam("prefix") final @Nulla Stream rules = ruleRegistry.stream() .map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider)); + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + cc.setNoStore(true); rules = dtoMapper.limitToFields(rules, "uid,templateUID,name,visibility,description,tags,editable"); - return Response.ok(new Stream2JSONInputStream(rules)).lastModified(cacheableListLastModified).build(); + return Response.ok(new Stream2JSONInputStream(rules)).lastModified(cacheableListLastModified) + .cacheControl(cc).build(); } // match all diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index dd543a2d649..956f1598859 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -43,6 +43,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -260,7 +261,13 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead .peek(dto -> dto.editable = isEditable(dto.name)); itemStream = dtoMapper.limitToFields(itemStream, "name,label,type,groupType,function,category,editable,groupNames,link,tags,metadata"); - return Response.ok(new Stream2JSONInputStream(itemStream)).lastModified(lastModifiedDate).build(); + + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + cc.setNoStore(true); + return Response.ok(new Stream2JSONInputStream(itemStream)).lastModified(lastModifiedDate).cacheControl(cc) + .build(); } Stream itemStream = getItems(type, tags).stream() // diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java index 61191e48fb8..f3577f8c755 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java @@ -42,6 +42,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; @@ -326,8 +327,13 @@ public Response getAll(@Context Request request, cacheableListLastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS)); } + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + cc.setNoStore(true); thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,location,editable"); - return Response.ok(new Stream2JSONInputStream(thingStream)).lastModified(cacheableListLastModified).build(); + return Response.ok(new Stream2JSONInputStream(thingStream)).lastModified(cacheableListLastModified) + .cacheControl(cc).build(); } if (summary != null && summary) { diff --git a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java index 210a499b887..eb34f73ce11 100644 --- a/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java +++ b/bundles/org.openhab.core.io.rest.ui/src/main/java/org/openhab/core/io/rest/ui/internal/UIResource.java @@ -31,6 +31,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; @@ -166,7 +167,11 @@ public Response getAllComponents(@Context Request request, @PathParam("namespace lastModifiedDates.put(namespace, lastModifiedDate); } - return Response.ok(new Stream2JSONInputStream(components)).lastModified(lastModifiedDate).build(); + CacheControl cc = new CacheControl(); + cc.setMustRevalidate(true); + cc.setPrivate(true); + return Response.ok(new Stream2JSONInputStream(components)).lastModified(lastModifiedDate).cacheControl(cc) + .build(); } } From 0ff529673af3553654721eb171806b59d4532118 Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Tue, 24 Jan 2023 12:00:34 +0100 Subject: [PATCH 3/8] Adjust cache control Signed-off-by: Yannick Schaus --- .../org/openhab/core/automation/rest/internal/RuleResource.java | 1 - .../openhab/core/io/rest/core/internal/item/ItemResource.java | 1 - .../openhab/core/io/rest/core/internal/thing/ThingResource.java | 1 - 3 files changed, 3 deletions(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index cbd8eb46657..c30a7917367 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -182,7 +182,6 @@ public Response get(@Context Request request, @QueryParam("prefix") final @Nulla CacheControl cc = new CacheControl(); cc.setMustRevalidate(true); cc.setPrivate(true); - cc.setNoStore(true); rules = dtoMapper.limitToFields(rules, "uid,templateUID,name,visibility,description,tags,editable"); return Response.ok(new Stream2JSONInputStream(rules)).lastModified(cacheableListLastModified) .cacheControl(cc).build(); diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 956f1598859..8e0ba187a37 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -265,7 +265,6 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead CacheControl cc = new CacheControl(); cc.setMustRevalidate(true); cc.setPrivate(true); - cc.setNoStore(true); return Response.ok(new Stream2JSONInputStream(itemStream)).lastModified(lastModifiedDate).cacheControl(cc) .build(); } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java index f3577f8c755..83af810d9f2 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java @@ -330,7 +330,6 @@ public Response getAll(@Context Request request, CacheControl cc = new CacheControl(); cc.setMustRevalidate(true); cc.setPrivate(true); - cc.setNoStore(true); thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,location,editable"); return Response.ok(new Stream2JSONInputStream(thingStream)).lastModified(cacheableListLastModified) .cacheControl(cc).build(); From d245303b80b066961be289c4f05b275a72ec2dde Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Tue, 24 Jan 2023 13:30:59 +0100 Subject: [PATCH 4/8] Fix ItemResourceOSGiTest Signed-off-by: Yannick Schaus --- .../internal/item/ItemResourceOSGiTest.java | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java b/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java index 5e684f4ed62..b39d4a01192 100644 --- a/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java +++ b/itests/org.openhab.core.io.rest.core.tests/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResourceOSGiTest.java @@ -30,6 +30,7 @@ import java.util.stream.Stream; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; @@ -94,6 +95,7 @@ public class ItemResourceOSGiTest extends JavaOSGiTest { private @Mock @NonNullByDefault({}) ItemProvider itemProviderMock; private @Mock @NonNullByDefault({}) UriBuilder uriBuilderMock; private @Mock @NonNullByDefault({}) UriInfo uriInfoMock; + private @Mock @NonNullByDefault({}) Request request; @BeforeEach public void beforeEach() { @@ -127,7 +129,8 @@ public void beforeEach() { public void shouldReturnUnicodeItems() throws IOException, TransformationException { item4.setLabel(ITEM_LABEL4); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, null, null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, null, null, false, + null, false); assertThat(readItemLabelsFromResponse(response), hasItems(ITEM_LABEL4)); } @@ -147,28 +150,31 @@ public void shouldFilterItemsByTag() throws Exception { item3.addTag("Tag2"); item4.addTag("Tag4"); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "Tag1", null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "Tag1", null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "Tag2", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "Tag2", null, false, null, + false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME2, ITEM_NAME3)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "NotExistingTag", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "NotExistingTag", null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @Test public void shouldFilterItemsByType() throws Exception { - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, CoreItemFactory.SWITCH, null, - null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, CoreItemFactory.SWITCH, + null, null, false, null, false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME1, ITEM_NAME2)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, CoreItemFactory.DIMMER, null, null, false, - null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, CoreItemFactory.DIMMER, null, + null, false, null, false); assertThat(readItemNamesFromResponse(response), hasItems(ITEM_NAME3)); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, CoreItemFactory.COLOR, null, null, false, - null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, CoreItemFactory.COLOR, null, null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @@ -176,15 +182,18 @@ public void shouldFilterItemsByType() throws Exception { public void shouldAddAndRemoveTags() throws Exception { managedItemProvider.add(new SwitchItem("Switch")); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, null); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, + false, null, false); assertThat(readItemNamesFromResponse(response), hasSize(0)); itemResource.addTag("Switch", "MyTag"); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, false, null, + false); assertThat(readItemNamesFromResponse(response), hasSize(1)); itemResource.removeTag("Switch", "MyTag"); - response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, null); + response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, false, null, + false); assertThat(readItemNamesFromResponse(response), hasSize(0)); } @@ -192,8 +201,8 @@ public void shouldAddAndRemoveTags() throws Exception { public void shouldIncludeRequestedFieldsOnly() throws Exception { managedItemProvider.add(new SwitchItem("Switch")); itemResource.addTag("Switch", "MyTag"); - Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, null, null, "MyTag", null, false, - "type,name"); + Response response = itemResource.getItems(uriInfoMock, httpHeadersMock, request, null, null, "MyTag", null, + false, "type,name", false); JsonElement result = JsonParser .parseString(new String(((InputStream) response.getEntity()).readAllBytes(), StandardCharsets.UTF_8)); From 40b50ccd990c77cd3d134b287c6c8c8ec09a0985 Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Tue, 13 Jun 2023 23:13:53 +0200 Subject: [PATCH 5/8] Address comments --- .../automation/rest/internal/RuleResource.java | 14 +++++++------- .../io/rest/core/internal/item/ItemResource.java | 4 ++-- .../io/rest/core/internal/thing/ThingResource.java | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index c30a7917367..4f398e64f63 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -163,9 +163,9 @@ void deactivate() { public Response get(@Context Request request, @QueryParam("prefix") final @Nullable String prefix, @QueryParam("tags") final @Nullable List tags, @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, - @DefaultValue("false") @QueryParam("cacheable") @Parameter(description = "provides a cacheable list and honors the If-Modified-Since header, all other parameters are ignored") boolean cacheable) { + @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and honors the If-Modified-Since header, all other parameters are ignored") boolean staticDataOnly) { - if (cacheable) { + if (staticDataOnly) { if (cacheableListLastModified != null) { Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified); if (responseBuilder != null) { @@ -593,25 +593,25 @@ public Response setModuleConfigParam(@PathParam("ruleUID") @Parameter(descriptio } } - private void resetCacheableListLastModified() { - cacheableListLastModified = null; + private void resetStaticListLastModified() { + staticListLastModified = null; } private class ResetLastModifiedChangeListener implements RegistryChangeListener { @Override public void added(Rule element) { - resetCacheableListLastModified(); + resetStaticListLastModified(); } @Override public void removed(Rule element) { - resetCacheableListLastModified(); + resetStaticListLastModified(); } @Override public void updated(Rule oldElement, Rule element) { - resetCacheableListLastModified(); + resetStaticListLastModified(); } } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java index 8e0ba187a37..9253a984133 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java @@ -235,13 +235,13 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead @QueryParam("metadata") @Parameter(description = "metadata selector - a comma separated list or a regular expression (suppressed if no value given)") @Nullable String namespaceSelector, @DefaultValue("false") @QueryParam("recursive") @Parameter(description = "get member items recursively") boolean recursive, @QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields, - @DefaultValue("false") @QueryParam("cacheable") @Parameter(description = "provides a cacheable list and checks the If-Modified-Since header, all other parameters are ignored except \"metadata\"") boolean cacheable) { + @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and checks the If-Modified-Since header, all other parameters are ignored except \"metadata\"") boolean staticDataOnly) { final Locale locale = localeService.getLocale(language); final Set namespaces = splitAndFilterNamespaces(namespaceSelector, locale); final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders); - if (cacheable) { + if (staticDataOnly) { Date lastModifiedDate = Date.from(Instant.now()); if (cacheableListsLastModified.containsKey(namespaceSelector)) { lastModifiedDate = cacheableListsLastModified.get(namespaceSelector); diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java index 83af810d9f2..c239ef54f51 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/thing/ThingResource.java @@ -310,13 +310,13 @@ public Response create( public Response getAll(@Context Request request, @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language, @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, - @DefaultValue("false") @QueryParam("cacheable") @Parameter(description = "provides a cacheable list and checks the If-Modified-Since header") boolean cacheable) { + @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and checks the If-Modified-Since header") boolean staticDataOnly) { final Locale locale = localeService.getLocale(language); Stream thingStream = thingRegistry.stream().map(t -> convertToEnrichedThingDTO(t, locale)) .distinct(); - if (cacheable) { + if (staticDataOnly) { if (cacheableListLastModified != null) { Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified); if (responseBuilder != null) { From 8cf3e9a12f897380d1a67ebc230062fea43387e5 Mon Sep 17 00:00:00 2001 From: Yannick Schaus Date: Thu, 15 Jun 2023 17:58:53 +0200 Subject: [PATCH 6/8] Add missing SecurityContext parameter -- Signed-off-by: Yannick Schaus --- .../org/openhab/core/automation/rest/internal/RuleResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index 6cc48db0962..04f1cbec9ee 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -164,7 +164,7 @@ void deactivate() { @Produces(MediaType.APPLICATION_JSON) @Operation(operationId = "getRules", summary = "Get available rules, optionally filtered by tags and/or prefix.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedRuleDTO.class)))) }) - public Response get(@Context Request request, @QueryParam("prefix") final @Nullable String prefix, + public Response get(@Context SecurityContext securityContext, @Context Request request, @QueryParam("prefix") final @Nullable String prefix, @QueryParam("tags") final @Nullable List tags, @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and honors the If-Modified-Since header, all other parameters are ignored") boolean staticDataOnly) { From 68307651ff43926c53dec750517f9649089304fb Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 15 Jun 2023 19:27:21 +0200 Subject: [PATCH 7/8] Update bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java Signed-off-by: J-N-K --- .../openhab/core/automation/rest/internal/RuleResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index 04f1cbec9ee..209adb4ff97 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -164,8 +164,8 @@ void deactivate() { @Produces(MediaType.APPLICATION_JSON) @Operation(operationId = "getRules", summary = "Get available rules, optionally filtered by tags and/or prefix.", responses = { @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedRuleDTO.class)))) }) - public Response get(@Context SecurityContext securityContext, @Context Request request, @QueryParam("prefix") final @Nullable String prefix, - @QueryParam("tags") final @Nullable List tags, + public Response get(@Context SecurityContext securityContext, @Context Request request, + @QueryParam("prefix") final @Nullable String prefix, @QueryParam("tags") final @Nullable List tags, @QueryParam("summary") @Parameter(description = "summary fields only") @Nullable Boolean summary, @DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and honors the If-Modified-Since header, all other parameters are ignored") boolean staticDataOnly) { From 7e5d1267285849211a7d5aa810fd945bb9c6f741 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Thu, 15 Jun 2023 19:46:34 +0200 Subject: [PATCH 8/8] Update bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java Signed-off-by: J-N-K --- .../org/openhab/core/automation/rest/internal/RuleResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java index 209adb4ff97..2ffb7adb75e 100644 --- a/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java +++ b/bundles/org.openhab.core.automation.rest/src/main/java/org/openhab/core/automation/rest/internal/RuleResource.java @@ -610,7 +610,7 @@ public Response setModuleConfigParam(@PathParam("ruleUID") @Parameter(descriptio } private void resetStaticListLastModified() { - staticListLastModified = null; + cacheableListLastModified = null; } private class ResetLastModifiedChangeListener implements RegistryChangeListener {