ViewerHelper.java
/*
* Copyright (C) 2025 B3Partners B.V.
*
* SPDX-License-Identifier: MIT
*/
package org.tailormap.api.persistence.helper;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import org.tailormap.api.persistence.Application;
import org.tailormap.api.persistence.GeoService;
import org.tailormap.api.persistence.TMFeatureSource;
import org.tailormap.api.persistence.TMFeatureType;
import org.tailormap.api.persistence.json.AppLayerSettings;
import org.tailormap.api.persistence.json.AppTreeLayerNode;
import org.tailormap.api.persistence.json.FeatureTypeRef;
import org.tailormap.api.persistence.json.GeoServiceLayer;
import org.tailormap.api.persistence.json.GeoServiceLayerSettings;
import org.tailormap.api.repository.FeatureSourceRepository;
import org.tailormap.api.repository.GeoServiceRepository;
@Service
public class ViewerHelper {
private final GeoServiceRepository geoServiceRepository;
private final FeatureSourceRepository featureSourceRepository;
public ViewerHelper(GeoServiceRepository geoServiceRepository, FeatureSourceRepository featureSourceRepository) {
this.geoServiceRepository = geoServiceRepository;
this.featureSourceRepository = featureSourceRepository;
}
public record AppLayerContext(
@NotNull AppTreeLayerNode node,
@NotNull AppLayerSettings appLayerSettings,
@NotNull GeoService geoService,
@NotNull GeoServiceLayer geoServiceLayer,
@Null FeatureTypeRef featureTypeRef) {}
/**
* Returns a map of AppTreeLayerNode id to AppLayerContext, with only nodes that reference an existing
* GeoServiceLayer, including base layer nodes.
*
* @param application the application
* @return a map of appLayerId to AppLayerContext
*/
public Map<String, AppLayerContext> getAppLayerContextMap(Application application) {
return getAppLayerContextMap(application, null);
}
/**
* As #getAppLayerContextMap(), but only for the given appLayerIds.
*
* @param application the application
* @param appLayerIds the list of appLayerIds to limit the result to
* @return a map of appLayerId to AppLayerContext
*/
public Map<String, AppLayerContext> getAppLayerContextMap(Application application, List<String> appLayerIds) {
Map<String, AppTreeLayerNode> nodeMap = new HashMap<>();
application.getAllAppTreeLayerNode().forEach(node -> {
if (appLayerIds == null || appLayerIds.contains(node.getId())) {
nodeMap.put(node.getId(), node);
}
});
// Efficiently retrieve all GeoServices in a single query
Map<String, GeoService> geoServiceMap =
geoServiceRepository
.findByIds(nodeMap.values().stream()
.map(AppTreeLayerNode::getServiceId)
.distinct()
.toList())
.stream()
.collect(Collectors.toMap(GeoService::getId, service -> service));
Map<String, AppLayerContext> contextMap = new HashMap<>();
for (Map.Entry<String, AppTreeLayerNode> entry : nodeMap.entrySet()) {
String appLayerId = entry.getKey();
AppTreeLayerNode lyrNode = entry.getValue();
GeoService service = geoServiceMap.get(lyrNode.getServiceId());
if (service == null) {
continue;
}
GeoServiceLayer layer = service.getLayers().stream()
.filter(l -> Objects.equals(l.getName(), lyrNode.getLayerName()))
.findFirst()
.orElse(null);
if (layer == null) {
continue;
}
GeoServiceLayerSettings lyrSettings = service.getLayerSettings(lyrNode.getLayerName());
if (lyrSettings == null) {
continue;
}
FeatureTypeRef ftr = lyrSettings.getFeatureType();
if (ftr == null) {
continue;
}
contextMap.put(
appLayerId,
new AppLayerContext(lyrNode, application.getAppLayerSettings(appLayerId), service, layer, ftr));
}
return contextMap;
}
public record AppLayerFullContext(
@NotNull AppTreeLayerNode node,
@NotNull AppLayerSettings appLayerSettings,
@NotNull GeoService geoService,
@NotNull GeoServiceLayer geoServiceLayer,
@NotNull TMFeatureType featureType) {}
/**
* As #getAppLayerContextMap(), but only with nodes that reference an existing GeoServiceLayer that has a feature
* type.
*
* @param application the application
* @return a map of appLayerId to AppLayerFullContext
*/
public Map<String, AppLayerFullContext> getAppLayerFullContextMap(Application application) {
return getAppLayerFullContextMap(application, null);
}
/**
* As #getAppLayerFullContextMap(), but only for the given appLayerIds.
*
* @param application the application
* @param appLayerIds the list of appLayerIds to limit the result to
* @return a map of appLayerId to AppLayerFullContext
*/
public Map<String, AppLayerFullContext> getAppLayerFullContextMap(
Application application, List<String> appLayerIds) {
Map<String, AppLayerContext> contextMap = getAppLayerContextMap(application, appLayerIds);
// Efficiently retrieve all TMFeatureSources in a single query
Map<Long, TMFeatureSource> featureSourceMap = featureSourceRepository
.findByIds(contextMap.values().stream()
.map(appLayerFeatureTypeRef ->
appLayerFeatureTypeRef.featureTypeRef().getFeatureSourceId())
.filter(Objects::nonNull)
.distinct()
.toList())
.stream()
.collect(Collectors.toMap(TMFeatureSource::getId, featureSource -> featureSource));
Map<String, AppLayerFullContext> fullContextMap = new HashMap<>();
for (Map.Entry<String, AppLayerContext> entry : contextMap.entrySet()) {
String appLayerId = entry.getKey();
AppLayerContext context = entry.getValue();
Optional.ofNullable(featureSourceMap.get(context.featureTypeRef().getFeatureSourceId()))
.map(featureSource -> featureSource.findFeatureTypeByName(
context.featureTypeRef().getFeatureTypeName()))
.ifPresent(featureType -> fullContextMap.put(
appLayerId,
new AppLayerFullContext(
context.node(),
context.appLayerSettings(),
context.geoService(),
context.geoServiceLayer(),
featureType)));
}
return fullContextMap;
}
}