Skip to content

Commit

Permalink
Fix for GWC not storing tiles when using datadir or jdbcconfig catalo…
Browse files Browse the repository at this point in the history
…g back-ends

Abstract out the PgconfigGwcInitializer as AbstractGwcInitializer and
refactor the default configuration classes to include the initializer.

Tiles weren't being stored when using the `datadir` or `jdbcconfig`
catalog backends because `ConfigurableBlobStore.setChanged()` wouldn't
be called at startup and it would hence bypass the storage, as its
internal `configured` flag was not set.
  • Loading branch information
groldan committed Sep 9, 2024
1 parent 0e04422 commit e889e21
Show file tree
Hide file tree
Showing 11 changed files with 360 additions and 211 deletions.
5 changes: 5 additions & 0 deletions src/gwc/autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,10 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@

/**
* {@link AutoConfiguration @AutoConfiguration} to set up the GeoServer {@link TileLayerCatalog}
* using the default implementation based on the {@link ResourceStore}
* using the default implementation based on the {@link ResourceStore}.
*
* <p>This default configuration applies if there's no other {@link GeoServerTileLayerConfiguration}
* provided.
*
* @see DefaultTileLayerCatalogConfiguration
* @see ConditionalOnGeoWebCacheEnabled
* @see PgconfigTileLayerCatalogAutoConfiguration
* @since 1.0
*/
@AutoConfiguration(after = PgconfigTileLayerCatalogAutoConfiguration.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,22 @@
*/
package org.geoserver.cloud.autoconfigure.gwc.backend;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Stopwatch;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.geoserver.cloud.gwc.backend.pgconfig.PgconfigTileLayerCatalog;
import org.geoserver.cloud.gwc.config.core.AbstractGwcInitializer;
import org.geoserver.cloud.gwc.event.TileLayerEvent;
import org.geoserver.cloud.gwc.repository.GeoServerTileLayerConfiguration;
import org.geoserver.config.GeoServer;
import org.geoserver.config.GeoServerReinitializer;
import org.geoserver.gwc.ConfigurableBlobStore;
import org.geoserver.gwc.config.GWCConfig;
import org.geoserver.gwc.config.GWCConfigPersister;
import org.geoserver.gwc.config.GWCInitializer;
import org.geoserver.gwc.layer.GeoServerTileLayer;
import org.geoserver.gwc.layer.TileLayerCatalog;
import org.geowebcache.config.TileLayerConfiguration;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.layer.TileLayerDispatcher;
import org.geowebcache.storage.blobstore.memory.CacheConfiguration;
import org.geowebcache.storage.blobstore.memory.CacheProvider;
import org.geowebcache.storage.blobstore.memory.guava.GuavaCacheProvider;
import org.springframework.context.event.EventListener;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;

/**
* Replacement for {@link GWCInitializer} when using the "pgconfig" storage.
Expand All @@ -57,151 +40,18 @@
*
* @since 1.8
*/
@RequiredArgsConstructor
@Slf4j(topic = "org.geoserver.cloud.autoconfigure.gwc.backend")
class PgconfigGwcInitializer implements GeoServerReinitializer {

private final @NonNull GWCConfigPersister configPersister;
private final @NonNull ConfigurableBlobStore blobStore;
private final @NonNull GeoServerTileLayerConfiguration geoseverTileLayers;

/**
* @see org.geoserver.config.GeoServerInitializer#initialize(org.geoserver.config.GeoServer)
*/
@Override
public void initialize(final GeoServer geoServer) throws Exception {
log.info("Initializing GeoServer specific GWC configuration from gwc-gs.xml");

final GWCConfig gwcConfig = configPersister.getConfig();
checkNotNull(gwcConfig);
class PgconfigGwcInitializer extends AbstractGwcInitializer {

configureInMemoryCacheProvider(gwcConfig);

final boolean initialization = true;
blobStore.setChanged(gwcConfig, initialization);

setUpNonMemoryCacheableLayers();
}

@EventListener(TileLayerEvent.class)
void onTileLayerEvent(TileLayerEvent event) {
cacheProvider()
.ifPresent(
cache -> {
switch (event.getEventType()) {
case DELETED:
log.debug(
"TileLayer {} deleted, notifying in-memory CacheProvider",
event.getName());
cache.removeUncachedLayer(event.getName());
break;
case MODIFIED:
if (event.getOldName() != null
&& !Objects.equals(
event.getOldName(), event.getName())) {
log.info(
"TileLayer {} renamed to {}, notifying in-memory CacheProvider",
event.getOldName(),
event.getName());
cache.removeUncachedLayer(event.getOldName());
}
setInMemoryLayerCaching(event.getName());
break;
default:
setInMemoryLayerCaching(event.getName());
break;
}
});
public PgconfigGwcInitializer(
@NonNull GWCConfigPersister configPersister,
@NonNull ConfigurableBlobStore blobStore,
@NonNull GeoServerTileLayerConfiguration geoseverTileLayers) {
super(configPersister, blobStore, geoseverTileLayers);
}

private void configureInMemoryCacheProvider(final GWCConfig gwcConfig) throws IOException {
// Setting default CacheProvider class if not present
if (gwcConfig.getCacheProviderClass() == null
|| gwcConfig.getCacheProviderClass().isEmpty()) {
gwcConfig.setCacheProviderClass(GuavaCacheProvider.class.toString());
configPersister.save(gwcConfig);
}

// Setting default Cache Configuration
if (gwcConfig.getCacheConfigurations() == null) {
log.debug("Setting default CacheConfiguration");
Map<String, CacheConfiguration> map = new HashMap<>();
map.put(GuavaCacheProvider.class.toString(), new CacheConfiguration());
gwcConfig.setCacheConfigurations(map);
configPersister.save(gwcConfig);
} else {
log.debug("CacheConfiguration loaded");
}

// Change ConfigurableBlobStore behavior
String cacheProviderClass = gwcConfig.getCacheProviderClass();
Map<String, CacheProvider> cacheProviders = blobStore.getCacheProviders();
if (!cacheProviders.containsKey(cacheProviderClass)) {
gwcConfig.setCacheProviderClass(GuavaCacheProvider.class.toString());
configPersister.save(gwcConfig);
log.debug("Unable to find: {}, used default configuration", cacheProviderClass);
}
}

private void setInMemoryLayerCaching(@NonNull String layerName) {

layer(layerName)
.ifPresentOrElse(this::addUncachedLayer, () -> removeCachedLayer(layerName));
}

private void removeCachedLayer(String layerName) {
cacheProvider()
.ifPresent(
cache -> {
log.debug(
"TileLayer {} does not exist, notifying CacheProvider",
layerName);
cache.removeLayer(layerName);
cache.removeUncachedLayer(layerName);
});
}

private void addUncachedLayer(GeoServerTileLayer tl) {
if (!tl.getInfo().isInMemoryCached()) {
log.debug(
"TileLayer {} is not to be memory cached, notifying CacheProvider",
tl.getName());
cacheProvider().ifPresent(cache -> cache.addUncachedLayer(tl.getName()));
}
}

private Optional<GeoServerTileLayer> layer(String layerName) {
return geoseverTileLayers
.getLayer(layerName)
.filter(GeoServerTileLayer.class::isInstance)
.map(GeoServerTileLayer.class::cast);
}

/**
* Private method for adding all the Layer that must not be cached to the {@link CacheProvider}
* instance.
*/
private void setUpNonMemoryCacheableLayers() {
cacheProvider()
.ifPresent(
cache -> {
// Add all the various Layers to avoid caching
log.info("Adding Layers to avoid In Memory Caching");
// it is ok to use the ForkJoinPool.commonPool() here, there's no I/O
// involved
Stopwatch sw = Stopwatch.createStarted();
Collection<? extends TileLayer> layers = geoseverTileLayers.getLayers();
log.info("Queried {} tile layers in {}", layers.size(), sw.stop());
layers.stream()
.filter(GeoServerTileLayer.class::isInstance)
.map(GeoServerTileLayer.class::cast)
.filter(l -> l.isEnabled() && !l.getInfo().isInMemoryCached())
.map(GeoServerTileLayer::getName)
.forEach(cache::addUncachedLayer);
});
}

private Optional<CacheProvider> cacheProvider() {
return Optional.ofNullable(blobStore.getCache());
@Override
protected Logger logger() {
return log;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
* GeoServer Catalog in the Postgres database;
*
* @since 1.7
* @see ConditionalOnPgconfigBackendEnabled
*/
@AutoConfiguration(after = PgconfigDataSourceAutoConfiguration.class)
@ConditionalOnClass(PgconfigTileLayerCatalog.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
package org.geoserver.cloud.autoconfigure.gwc.backend;

import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -14,6 +16,7 @@
import com.google.common.base.Throwables;

import org.geoserver.cloud.autoconfigure.gwc.GeoWebCacheContextRunner;
import org.geoserver.cloud.gwc.config.core.GeoWebCacheConfigurationProperties;
import org.geoserver.cloud.gwc.repository.CloudDefaultStorageFinder;
import org.geoserver.cloud.gwc.repository.CloudGwcXmlConfiguration;
import org.geoserver.cloud.gwc.repository.CloudXMLResourceProvider;
Expand All @@ -37,6 +40,7 @@ class GwcCoreAutoConfigurationTest {

@BeforeEach
void setUp() {
System.clearProperty("GEOWEBCACHE_CACHE_DIR");
runner = GeoWebCacheContextRunner.newMinimalGeoWebCacheContextRunner(tmpDir);
}

Expand Down Expand Up @@ -92,4 +96,54 @@ protected void assertContextLoadFails(
assertThat(root.getMessage(), containsString(expectedMessage));
});
}

/**
* Note this test will fail without {@code --add-opens=java.base/java.util=ALL-UNNAMED} JVM
* parameter (watch out if running it from the IDE, maven is configured to set it)
*/
@Test
void defaultCacheDirectoryFromEnvVariable() throws Exception {
File dir = new File(tmpDir, "env_cachedir");
assertThat(dir).doesNotExist();
String dirpath = dir.getAbsolutePath();
withEnvironmentVariable("GEOWEBCACHE_CACHE_DIR", dirpath)
.execute(
() -> {
assertThat(System.getenv("GEOWEBCACHE_CACHE_DIR")).isEqualTo(dirpath);
runner.withPropertyValues(
"gwc.cache-directory: ${GEOWEBCACHE_CACHE_DIR}")
.run(
context -> {
assertThat(context).hasNotFailed();
assertThat(dir).isDirectory();

var gwcConfigProps =
context.getBean(
GeoWebCacheConfigurationProperties
.class);
assertThat(gwcConfigProps.getCacheDirectory())
.isEqualTo(dir.toPath());
});
});
assertThat(System.getenv("GEOWEBCACHE_CACHE_DIR")).isNull();
}

@Test
void defaultCacheDirectoryFromSystemProperty() throws Exception {
File dir = new File(tmpDir, "sysprop_cachedir");
assertThat(dir).doesNotExist();
String dirpath = dir.getAbsolutePath();

System.setProperty("GEOWEBCACHE_CACHE_DIR", dirpath);
try {
runner.withPropertyValues("gwc.cache-directory: ${GEOWEBCACHE_CACHE_DIR}")
.run(
context -> {
assertThat(context).hasNotFailed();
assertThat(dir).isDirectory();
});
} finally {
System.clearProperty("GEOWEBCACHE_CACHE_DIR");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import org.geoserver.cloud.gwc.repository.CloudDefaultStorageFinder;
import org.geoserver.cloud.gwc.repository.CloudGwcXmlConfiguration;
import org.geoserver.cloud.gwc.repository.CloudXMLResourceProvider;
import org.geoserver.gwc.config.GwcGeoserverConfigurationInitializer;
import org.geoserver.gwc.config.DefaultGwcInitializer;
import org.geoserver.platform.GeoServerExtensionsHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -72,11 +72,9 @@ void contextLoads() {
GeoServerExtensionsHelper.init(context);
assertThat(context)
.hasNotFailed()
.hasBean("gwcGeoserverConfigurationInitializer")
.getBean(
"gwcGeoserverConfigurationInitializer",
GwcGeoserverConfigurationInitializer.class)
.isInstanceOf(GwcGeoserverConfigurationInitializer.class);
.hasBean("gwcInitializer")
.getBean("gwcInitializer")
.isInstanceOf(DefaultGwcInitializer.class);

assertThat(context.isTypeMatch("gwcXmlConfig", CloudGwcXmlConfiguration.class))
.isTrue();
Expand Down
Loading

0 comments on commit e889e21

Please sign in to comment.