Skip to content

Commit

Permalink
[rest] Add caching for TagResource & De-duplicate code for caching (o…
Browse files Browse the repository at this point in the history
…penhab#3729)

* [rest] Add caching for TagResource
* [core] Add & use RegistryChangedRunnableListener class

Signed-off-by: Florian Hotze <[email protected]>
  • Loading branch information
florian-h05 authored Jul 25, 2023
1 parent 79fd459 commit e1741cf
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,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.common.registry.RegistryChangedRunnableListener;
import org.openhab.core.config.core.ConfigUtil;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.events.Event;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -608,26 +609,4 @@ public Response setModuleConfigParam(@PathParam("ruleUID") @Parameter(descriptio
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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,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.common.registry.RegistryChangedRunnableListener;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.io.rest.DTOMapper;
import org.openhab.core.io.rest.JSONResponse;
Expand All @@ -74,7 +74,6 @@
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;
Expand Down Expand Up @@ -183,8 +182,15 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context
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<>();

Expand Down Expand Up @@ -990,48 +996,4 @@ private void addMetadata(EnrichedItemDTO dto, Set<String> 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<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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,26 +29,31 @@
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;

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;
Expand Down Expand Up @@ -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

Expand All @@ -93,21 +105,43 @@ public TagResource(final @Reference LocaleService localeService,
this.localeService = localeService;
this.semanticTagRegistry = semanticTagRegistry;
this.managedSemanticTagProvider = managedSemanticTagProvider;

this.semanticTagRegistry.addRegistryChangeListener(resetLastModifiedChangeListener);
}

@Deactivate
void deactivate() {
this.semanticTagRegistry.removeRegistryChangeListener(resetLastModifiedChangeListener);
}

@GET
@RolesAllowed({ Role.USER, Role.ADMIN })
@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
Expand All @@ -117,19 +151,33 @@ public Response getTags(final @Context UriInfo uriInfo, final @Context HttpHeade
@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!");
}
Expand Down Expand Up @@ -187,8 +235,6 @@ public Response create(
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,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.common.registry.RegistryChangedRunnableListener;
import org.openhab.core.config.core.ConfigDescription;
import org.openhab.core.config.core.ConfigDescriptionRegistry;
import org.openhab.core.config.core.ConfigUtil;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -890,26 +891,4 @@ private URI getConfigDescriptionURI(ChannelUID channelUID) {
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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit e1741cf

Please sign in to comment.