mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-25 11:45:49 +01:00
[rest] Add caching for add-on resource (#4107)
* [rest] Introduce a CACHE_CONTROL constant * [rest] Add caching for add-ons endpoint Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
This commit is contained in:
parent
bf8b131701
commit
7f47d825a0
@ -38,7 +38,6 @@ import javax.ws.rs.Path;
|
||||
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;
|
||||
@ -135,10 +134,10 @@ public class RuleResource implements RESTResource {
|
||||
private final RuleRegistry ruleRegistry;
|
||||
private final ManagedRuleProvider managedRuleProvider;
|
||||
private final RegistryChangedRunnableListener<Rule> resetLastModifiedChangeListener = new RegistryChangedRunnableListener<>(
|
||||
() -> cacheableListLastModified = null);
|
||||
() -> lastModified = null);
|
||||
|
||||
private @Context @NonNullByDefault({}) UriInfo uriInfo;
|
||||
private @Nullable Date cacheableListLastModified = null;
|
||||
private @Nullable Date lastModified = null;
|
||||
|
||||
@Activate
|
||||
public RuleResource( //
|
||||
@ -174,26 +173,22 @@ public class RuleResource implements RESTResource {
|
||||
}
|
||||
|
||||
if (staticDataOnly) {
|
||||
if (cacheableListLastModified != null) {
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified);
|
||||
if (lastModified != null) {
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModified);
|
||||
if (responseBuilder != null) {
|
||||
// send 304 Not Modified
|
||||
return responseBuilder.build();
|
||||
}
|
||||
} else {
|
||||
cacheableListLastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
Stream<EnrichedRuleDTO> rules = ruleRegistry.stream()
|
||||
.map(rule -> EnrichedRuleDTOMapper.map(rule, ruleManager, managedRuleProvider));
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setNoCache(true);
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
rules = dtoMapper.limitToFields(rules, "uid,templateUID,name,visibility,description,tags,editable");
|
||||
return Response.ok(new Stream2JSONInputStream(rules)).lastModified(cacheableListLastModified)
|
||||
.cacheControl(cc).build();
|
||||
return Response.ok(new Stream2JSONInputStream(rules)).lastModified(lastModified)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
|
||||
// match all
|
||||
|
@ -16,7 +16,10 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.Collator;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@ -37,6 +40,7 @@ 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 javax.ws.rs.core.UriInfo;
|
||||
@ -45,6 +49,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.core.addon.Addon;
|
||||
import org.openhab.core.addon.AddonEvent;
|
||||
import org.openhab.core.addon.AddonEventFactory;
|
||||
import org.openhab.core.addon.AddonInfo;
|
||||
import org.openhab.core.addon.AddonInfoRegistry;
|
||||
@ -59,6 +64,7 @@ import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.addon.AddonSuggestionService;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
@ -106,13 +112,14 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@SecurityRequirement(name = "oauth2", scopes = { "admin" })
|
||||
@Tag(name = AddonResource.PATH_ADDONS)
|
||||
@NonNullByDefault
|
||||
public class AddonResource implements RESTResource {
|
||||
public class AddonResource implements RESTResource, EventSubscriber {
|
||||
|
||||
private static final String THREAD_POOL_NAME = "addonService";
|
||||
|
||||
public static final String PATH_ADDONS = "addons";
|
||||
|
||||
public static final String DEFAULT_ADDON_SERVICE = "karaf";
|
||||
private static final Set<String> SUBSCRIBED_EVENT_TYPES = Set.of(AddonEvent.TYPE);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AddonResource.class);
|
||||
private final Set<AddonService> addonServices = new CopyOnWriteArraySet<>();
|
||||
@ -123,6 +130,8 @@ public class AddonResource implements RESTResource {
|
||||
private final ConfigDescriptionRegistry configDescriptionRegistry;
|
||||
private final AddonSuggestionService addonSuggestionService;
|
||||
|
||||
private @Nullable Date lastModified = null;
|
||||
|
||||
private @Context @NonNullByDefault({}) UriInfo uriInfo;
|
||||
|
||||
@Activate
|
||||
@ -142,30 +151,59 @@ public class AddonResource implements RESTResource {
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void addAddonService(AddonService featureService) {
|
||||
this.addonServices.add(featureService);
|
||||
lastModified = null;
|
||||
}
|
||||
|
||||
protected void removeAddonService(AddonService featureService) {
|
||||
this.addonServices.remove(featureService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getSubscribedEventTypes() {
|
||||
return SUBSCRIBED_EVENT_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(Event event) {
|
||||
lastModified = null;
|
||||
}
|
||||
|
||||
private boolean lastModifiedIsValid() {
|
||||
if (lastModified == null)
|
||||
return false;
|
||||
return (new Date().getTime() - lastModified.getTime()) <= 450 * 1000;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(operationId = "getAddons", summary = "Get all add-ons.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Addon.class)))),
|
||||
@ApiResponse(responseCode = "404", description = "Service not found") })
|
||||
public Response getAddon(
|
||||
public Response getAddon(final @Context Request request,
|
||||
@HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language,
|
||||
@QueryParam("serviceId") @Parameter(description = "service ID") @Nullable String serviceId) {
|
||||
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
|
||||
Locale locale = localeService.getLocale(language);
|
||||
if (lastModifiedIsValid()) {
|
||||
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));
|
||||
}
|
||||
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
if ("all".equals(serviceId)) {
|
||||
return Response.ok(new Stream2JSONInputStream(getAllAddons(locale))).build();
|
||||
return Response.ok(new Stream2JSONInputStream(getAllAddons(locale))).lastModified(lastModified)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
} else {
|
||||
AddonService addonService = (serviceId != null) ? getServiceById(serviceId) : getDefaultService();
|
||||
if (addonService == null) {
|
||||
return Response.status(HttpStatus.NOT_FOUND_404).build();
|
||||
}
|
||||
return Response.ok(new Stream2JSONInputStream(addonService.getAddons(locale).stream())).build();
|
||||
return Response.ok(new Stream2JSONInputStream(addonService.getAddons(locale).stream()))
|
||||
.lastModified(lastModified).cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,12 +212,23 @@ public class AddonResource implements RESTResource {
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(operationId = "getAddonTypes", summary = "Get all add-on types.", responses = {
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = AddonType.class)))) })
|
||||
public Response getServices(
|
||||
public Response getServices(final @Context Request request,
|
||||
@HeaderParam("Accept-Language") @Parameter(description = "language") @Nullable String language) {
|
||||
logger.debug("Received HTTP GET request at '{}'", uriInfo.getPath());
|
||||
Locale locale = localeService.getLocale(language);
|
||||
if (lastModifiedIsValid()) {
|
||||
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));
|
||||
}
|
||||
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
Stream<AddonServiceDTO> addonTypeStream = addonServices.stream().map(s -> convertToAddonServiceDTO(s, locale));
|
||||
return Response.ok(new Stream2JSONInputStream(addonTypeStream)).build();
|
||||
return Response.ok(new Stream2JSONInputStream(addonTypeStream)).lastModified(lastModified)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
|
@ -42,7 +42,6 @@ import javax.ws.rs.Path;
|
||||
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;
|
||||
@ -182,16 +181,12 @@ public class ItemResource implements RESTResource {
|
||||
private final MetadataSelectorMatcher metadataSelectorMatcher;
|
||||
private final SemanticTagRegistry semanticTagRegistry;
|
||||
|
||||
private void resetCacheableListsLastModified() {
|
||||
this.cacheableListsLastModified.clear();
|
||||
}
|
||||
|
||||
private final RegistryChangedRunnableListener<Item> resetLastModifiedItemChangeListener = new RegistryChangedRunnableListener<>(
|
||||
this::resetCacheableListsLastModified);
|
||||
() -> lastModified = null);
|
||||
private final RegistryChangedRunnableListener<Metadata> resetLastModifiedMetadataChangeListener = new RegistryChangedRunnableListener<>(
|
||||
this::resetCacheableListsLastModified);
|
||||
() -> lastModified = null);
|
||||
|
||||
private Map<@Nullable String, Date> cacheableListsLastModified = new HashMap<>();
|
||||
private @Nullable Date lastModified = null;
|
||||
|
||||
@Activate
|
||||
public ItemResource(//
|
||||
@ -250,17 +245,14 @@ public class ItemResource implements RESTResource {
|
||||
final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders);
|
||||
|
||||
if (staticDataOnly) {
|
||||
Date lastModifiedDate = Date.from(Instant.now());
|
||||
if (cacheableListsLastModified.containsKey(namespaceSelector)) {
|
||||
lastModifiedDate = cacheableListsLastModified.get(namespaceSelector);
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModifiedDate);
|
||||
if (lastModified != null) {
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModified);
|
||||
if (responseBuilder != null) {
|
||||
// send 304 Not Modified
|
||||
return responseBuilder.build();
|
||||
}
|
||||
} else {
|
||||
lastModifiedDate = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
cacheableListsLastModified.put(namespaceSelector, lastModifiedDate);
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
Stream<EnrichedItemDTO> itemStream = getItems(type, tags).stream() //
|
||||
@ -270,12 +262,8 @@ public class ItemResource implements RESTResource {
|
||||
itemStream = dtoMapper.limitToFields(itemStream,
|
||||
"name,label,type,groupType,function,category,editable,groupNames,link,tags,metadata,commandDescription,stateDescription");
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setNoCache(true);
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
return Response.ok(new Stream2JSONInputStream(itemStream)).lastModified(lastModifiedDate).cacheControl(cc)
|
||||
.build();
|
||||
return Response.ok(new Stream2JSONInputStream(itemStream)).lastModified(lastModified)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
|
||||
Stream<EnrichedItemDTO> itemStream = getItems(type, tags).stream() //
|
||||
|
@ -29,7 +29,6 @@ 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;
|
||||
@ -132,17 +131,13 @@ public class TagResource implements RESTResource {
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setNoCache(true);
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
|
||||
Stream<EnrichedSemanticTagDTO> tagsStream = semanticTagRegistry.getAll().stream()
|
||||
.sorted(Comparator.comparing(SemanticTag::getUID))
|
||||
.map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t)));
|
||||
return Response.ok(new Stream2JSONInputStream(tagsStream)).lastModified(lastModified).cacheControl(cc).build();
|
||||
return Response.ok(new Stream2JSONInputStream(tagsStream)).lastModified(lastModified)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@ -165,11 +160,6 @@ public class TagResource implements RESTResource {
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setNoCache(true);
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
String uid = tagId.trim();
|
||||
|
||||
@ -178,8 +168,8 @@ public class TagResource implements RESTResource {
|
||||
Stream<EnrichedSemanticTagDTO> tagsStream = semanticTagRegistry.getSubTree(tag).stream()
|
||||
.sorted(Comparator.comparing(SemanticTag::getUID))
|
||||
.map(t -> new EnrichedSemanticTagDTO(t.localized(locale), semanticTagRegistry.isEditable(t)));
|
||||
return Response.ok(new Stream2JSONInputStream(tagsStream)).lastModified(lastModified).cacheControl(cc)
|
||||
.build();
|
||||
return Response.ok(new Stream2JSONInputStream(tagsStream)).lastModified(lastModified)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
} else {
|
||||
return JSONResponse.createErrorResponse(Status.NOT_FOUND, "Tag " + uid + " does not exist!");
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ import javax.ws.rs.Path;
|
||||
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;
|
||||
@ -171,10 +170,10 @@ public class ThingResource implements RESTResource {
|
||||
private final ThingStatusInfoI18nLocalizationService thingStatusInfoI18nLocalizationService;
|
||||
private final ThingTypeRegistry thingTypeRegistry;
|
||||
private final RegistryChangedRunnableListener<Thing> resetLastModifiedChangeListener = new RegistryChangedRunnableListener<>(
|
||||
() -> cacheableListLastModified = null);
|
||||
() -> lastModified = null);
|
||||
|
||||
private @Context @NonNullByDefault({}) UriInfo uriInfo;
|
||||
private @Nullable Date cacheableListLastModified = null;
|
||||
private @Nullable Date lastModified = null;
|
||||
|
||||
@Activate
|
||||
public ThingResource( //
|
||||
@ -317,23 +316,19 @@ public class ThingResource implements RESTResource {
|
||||
.distinct();
|
||||
|
||||
if (staticDataOnly) {
|
||||
if (cacheableListLastModified != null) {
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(cacheableListLastModified);
|
||||
if (lastModified != null) {
|
||||
Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(lastModified);
|
||||
if (responseBuilder != null) {
|
||||
// send 304 Not Modified
|
||||
return responseBuilder.build();
|
||||
}
|
||||
} else {
|
||||
cacheableListLastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setNoCache(true);
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
thingStream = dtoMapper.limitToFields(thingStream, "UID,label,bridgeUID,thingTypeUID,location,editable");
|
||||
return Response.ok(new Stream2JSONInputStream(thingStream)).lastModified(cacheableListLastModified)
|
||||
.cacheControl(cc).build();
|
||||
return Response.ok(new Stream2JSONInputStream(thingStream)).lastModified(lastModified)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
|
||||
if (summary != null && summary) {
|
||||
|
@ -31,7 +31,6 @@ import javax.ws.rs.Path;
|
||||
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;
|
||||
@ -167,12 +166,8 @@ public class UIResource implements RESTResource {
|
||||
lastModifiedDates.put(namespace, lastModifiedDate);
|
||||
}
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setNoCache(true);
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
return Response.ok(new Stream2JSONInputStream(components)).lastModified(lastModifiedDate).cacheControl(cc)
|
||||
.build();
|
||||
return Response.ok(new Stream2JSONInputStream(components)).lastModified(lastModifiedDate)
|
||||
.cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest;
|
||||
|
||||
import javax.ws.rs.core.CacheControl;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
@ -38,4 +40,11 @@ public class RESTConstants {
|
||||
* Version 6: extended chart period parameter format (#3863)
|
||||
*/
|
||||
public static final String API_VERSION = "6";
|
||||
|
||||
public static final CacheControl CACHE_CONTROL = new CacheControl();
|
||||
static {
|
||||
CACHE_CONTROL.setNoCache(true);
|
||||
CACHE_CONTROL.setMustRevalidate(true);
|
||||
CACHE_CONTROL.setPrivate(true);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import javax.annotation.security.RolesAllowed;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.CacheControl;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Request;
|
||||
@ -118,12 +117,7 @@ public class SystemInfoResource implements RESTResource, ConfigurationListener {
|
||||
lastModified = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
|
||||
}
|
||||
|
||||
CacheControl cc = new CacheControl();
|
||||
cc.setNoCache(true);
|
||||
cc.setMustRevalidate(true);
|
||||
cc.setPrivate(true);
|
||||
|
||||
final UoMInfoBean bean = new UoMInfoBean(unitProvider);
|
||||
return Response.ok(bean).lastModified(lastModified).cacheControl(cc).build();
|
||||
return Response.ok(bean).lastModified(lastModified).cacheControl(RESTConstants.CACHE_CONTROL).build();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user