mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
[rest] Add caching for TagResource & De-duplicate code for caching (#3729)
* [rest] Add caching for TagResource * [core] Add & use RegistryChangedRunnableListener class Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
This commit is contained in:
parent
79fd459f59
commit
e1741cf61d
@ -74,7 +74,7 @@ import org.openhab.core.automation.rest.internal.dto.EnrichedRuleDTO;
|
||||
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.common.registry.RegistryChangedRunnableListener;
|
||||
import org.openhab.core.config.core.ConfigUtil;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.events.Event;
|
||||
@ -135,7 +135,8 @@ public class RuleResource implements RESTResource {
|
||||
private final RuleManager ruleManager;
|
||||
private final RuleRegistry ruleRegistry;
|
||||
private final ManagedRuleProvider managedRuleProvider;
|
||||
private final ResetLastModifiedChangeListener resetLastModifiedChangeListener = new ResetLastModifiedChangeListener();
|
||||
private final RegistryChangedRunnableListener<Rule> resetLastModifiedChangeListener = new RegistryChangedRunnableListener<>(
|
||||
() -> cacheableListLastModified = null);
|
||||
|
||||
private @Context @NonNullByDefault({}) UriInfo uriInfo;
|
||||
private @Nullable Date cacheableListLastModified = null;
|
||||
@ -608,26 +609,4 @@ public class RuleResource implements RESTResource {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetStaticListLastModified() {
|
||||
cacheableListLastModified = null;
|
||||
}
|
||||
|
||||
private class ResetLastModifiedChangeListener implements RegistryChangeListener<Rule> {
|
||||
|
||||
@Override
|
||||
public void added(Rule element) {
|
||||
resetStaticListLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Rule element) {
|
||||
resetStaticListLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Rule oldElement, Rule element) {
|
||||
resetStaticListLastModified();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ import javax.ws.rs.core.UriInfo;
|
||||
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.common.registry.RegistryChangedRunnableListener;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.io.rest.DTOMapper;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
@ -74,7 +74,6 @@ import org.openhab.core.items.Item;
|
||||
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;
|
||||
@ -183,8 +182,15 @@ public class ItemResource implements RESTResource {
|
||||
private final MetadataRegistry metadataRegistry;
|
||||
private final MetadataSelectorMatcher metadataSelectorMatcher;
|
||||
private final SemanticTagRegistry semanticTagRegistry;
|
||||
private final ItemRegistryChangeListener resetLastModifiedItemChangeListener = new ResetLastModifiedItemChangeListener();
|
||||
private final RegistryChangeListener<Metadata> resetLastModifiedMetadataChangeListener = new ResetLastModifiedMetadataChangeListener();
|
||||
|
||||
private void resetCacheableListsLastModified() {
|
||||
this.cacheableListsLastModified.clear();
|
||||
}
|
||||
|
||||
private final RegistryChangedRunnableListener<Item> resetLastModifiedItemChangeListener = new RegistryChangedRunnableListener<>(
|
||||
this::resetCacheableListsLastModified);
|
||||
private final RegistryChangedRunnableListener<Metadata> resetLastModifiedMetadataChangeListener = new RegistryChangedRunnableListener<>(
|
||||
this::resetCacheableListsLastModified);
|
||||
|
||||
private Map<@Nullable String, Date> cacheableListsLastModified = new HashMap<>();
|
||||
|
||||
@ -990,48 +996,4 @@ public class ItemResource implements RESTResource {
|
||||
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<String> oldItemNames) {
|
||||
resetCacheableListsLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Item element) {
|
||||
resetCacheableListsLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Item oldElement, Item element) {
|
||||
resetCacheableListsLastModified();
|
||||
}
|
||||
}
|
||||
|
||||
private class ResetLastModifiedMetadataChangeListener implements RegistryChangeListener<Metadata> {
|
||||
|
||||
@Override
|
||||
public void added(Metadata element) {
|
||||
resetCacheableListsLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Metadata element) {
|
||||
resetCacheableListsLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Metadata oldElement, Metadata element) {
|
||||
resetCacheableListsLastModified();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,12 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.core.internal.tag;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
import javax.ws.rs.Consumes;
|
||||
@ -26,9 +29,11 @@ import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.CacheControl;
|
||||
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;
|
||||
@ -36,16 +41,19 @@ import javax.ws.rs.core.UriInfo;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.common.registry.RegistryChangedRunnableListener;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
import org.openhab.core.io.rest.RESTResource;
|
||||
import org.openhab.core.io.rest.Stream2JSONInputStream;
|
||||
import org.openhab.core.semantics.ManagedSemanticTagProvider;
|
||||
import org.openhab.core.semantics.SemanticTag;
|
||||
import org.openhab.core.semantics.SemanticTagImpl;
|
||||
import org.openhab.core.semantics.SemanticTagRegistry;
|
||||
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;
|
||||
@ -83,6 +91,10 @@ public class TagResource implements RESTResource {
|
||||
private final LocaleService localeService;
|
||||
private final SemanticTagRegistry semanticTagRegistry;
|
||||
private final ManagedSemanticTagProvider managedSemanticTagProvider;
|
||||
private final RegistryChangedRunnableListener<SemanticTag> resetLastModifiedChangeListener = new RegistryChangedRunnableListener<>(
|
||||
() -> lastModified = null);
|
||||
|
||||
private @Nullable Date lastModified = null;
|
||||
|
||||
// TODO pattern in @Path
|
||||
|
||||
@ -93,6 +105,13 @@ public class TagResource implements RESTResource {
|
||||
this.localeService = localeService;
|
||||
this.semanticTagRegistry = semanticTagRegistry;
|
||||
this.managedSemanticTagProvider = managedSemanticTagProvider;
|
||||
|
||||
this.semanticTagRegistry.addRegistryChangeListener(resetLastModifiedChangeListener);
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
void deactivate() {
|
||||
this.semanticTagRegistry.removeRegistryChangeListener(resetLastModifiedChangeListener);
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -100,14 +119,29 @@ public class TagResource implements RESTResource {
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(operationId = "getSemanticTags", summary = "Get all available semantic tags.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedSemanticTagDTO.class)))) })
|
||||
public Response getTags(final @Context UriInfo uriInfo, final @Context HttpHeaders httpHeaders,
|
||||
public Response getTags(final @Context Request request, final @Context UriInfo uriInfo,
|
||||
final @Context HttpHeaders httpHeaders,
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language) {
|
||||
if (lastModified != null) {
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModified);
|
||||
if (responseBuilder != null) {
|
||||
// send 304 Not Modified
|
||||
return responseBuilder.build();
|
||||
}
|
||||
} else {
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
|
||||
List<EnrichedSemanticTagDTO> tagsDTO = semanticTagRegistry.getAll().stream()
|
||||
Stream<EnrichedSemanticTagDTO> tagsStream = semanticTagRegistry.getAll().stream()
|
||||
.sorted(Comparator.comparing(SemanticTag::getUID))
|
||||
.map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t))).toList();
|
||||
return JSONResponse.createResponse(Status.OK, tagsDTO, null);
|
||||
.map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t)));
|
||||
return Response.ok(new Stream2JSONInputStream(tagsStream)).lastModified(lastModified).cacheControl(cc).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -117,19 +151,33 @@ public class TagResource implements RESTResource {
|
||||
@Operation(operationId = "getSemanticTagAndSubTags", summary = "Gets a semantic tag and its sub tags.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = EnrichedSemanticTagDTO.class)))),
|
||||
@ApiResponse(responseCode = "404", description = "Semantic tag not found.") })
|
||||
public Response getTagAndSubTags(
|
||||
public Response getTagAndSubTags(final @Context Request request,
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
|
||||
@PathParam("tagId") @Parameter(description = "tag id") String tagId) {
|
||||
if (lastModified != null) {
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModified);
|
||||
if (responseBuilder != null) {
|
||||
// send 304 Not Modified
|
||||
return responseBuilder.build();
|
||||
}
|
||||
} else {
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
String uid = tagId.trim();
|
||||
|
||||
SemanticTag tag = semanticTagRegistry.get(uid);
|
||||
if (tag != null) {
|
||||
List<EnrichedSemanticTagDTO> tagsDTO = semanticTagRegistry.getSubTree(tag).stream()
|
||||
Stream<EnrichedSemanticTagDTO> tagsStream = semanticTagRegistry.getSubTree(tag).stream()
|
||||
.sorted(Comparator.comparing(SemanticTag::getUID))
|
||||
.map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t)))
|
||||
.toList();
|
||||
return JSONResponse.createResponse(Status.OK, tagsDTO, null);
|
||||
.map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t)));
|
||||
return Response.ok(new Stream2JSONInputStream(tagsStream)).lastModified(lastModified).cacheControl(cc)
|
||||
.build();
|
||||
} else {
|
||||
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "Tag " + uid + " does not exist!");
|
||||
}
|
||||
@ -187,8 +235,6 @@ public class TagResource implements RESTResource {
|
||||
public Response remove(
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
|
||||
@PathParam("tagId") @Parameter(description = "tag id") String tagId) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
|
||||
String uid = tagId.trim();
|
||||
|
||||
// check whether tag exists and throw 404 if not
|
||||
|
@ -54,7 +54,7 @@ import javax.ws.rs.core.UriInfo;
|
||||
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.common.registry.RegistryChangedRunnableListener;
|
||||
import org.openhab.core.config.core.ConfigDescription;
|
||||
import org.openhab.core.config.core.ConfigDescriptionRegistry;
|
||||
import org.openhab.core.config.core.ConfigUtil;
|
||||
@ -171,7 +171,8 @@ public class ThingResource implements RESTResource {
|
||||
private final ThingRegistry thingRegistry;
|
||||
private final ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService;
|
||||
private final ThingTypeRegistry thingTypeRegistry;
|
||||
private final ResetLastModifiedChangeListener resetLastModifiedChangeListener = new ResetLastModifiedChangeListener();
|
||||
private final RegistryChangedRunnableListener<Thing> resetLastModifiedChangeListener = new RegistryChangedRunnableListener<>(
|
||||
() -> cacheableListLastModified = null);
|
||||
|
||||
private @Context @NonNullByDefault({}) UriInfo uriInfo;
|
||||
private @Nullable Date cacheableListLastModified = null;
|
||||
@ -890,26 +891,4 @@ public class ThingResource implements RESTResource {
|
||||
throw new BadRequestException("Invalid URI syntax: " + uriString);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetCacheableListLastModified() {
|
||||
cacheableListLastModified = null;
|
||||
}
|
||||
|
||||
private class ResetLastModifiedChangeListener implements RegistryChangeListener<Thing> {
|
||||
|
||||
@Override
|
||||
public void added(Thing element) {
|
||||
resetCacheableListLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Thing element) {
|
||||
resetCacheableListLastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Thing oldElement, Thing element) {
|
||||
resetCacheableListLastModified();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.common.registry;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link RegistryChangedRunnableListener} can be added to {@link Registry} services, to execute a given
|
||||
* {@link Runnable} on all types of changes.
|
||||
*
|
||||
* @author Florian Hotze - Initial contribution
|
||||
*
|
||||
* @param <E> type of the element in the registry
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RegistryChangedRunnableListener<E> implements RegistryChangeListener<E> {
|
||||
final Runnable runnable;
|
||||
|
||||
public RegistryChangedRunnableListener(Runnable runnable) {
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(E element) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(E element) {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(E oldElement, E newElement) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user