/** * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * @author Gabriel Roldan, Boundless Spatial Inc, Copyright 2015 */ package org.geowebcache.storage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.geowebcache.GeoWebCacheException; import org.geowebcache.config.BlobStoreConfig; import org.geowebcache.config.ConfigurationException; import org.geowebcache.config.FileBlobStoreConfig; import org.geowebcache.config.XMLConfiguration; import org.geowebcache.layer.TileLayer; import org.geowebcache.layer.TileLayerDispatcher; import org.geowebcache.mime.MimeException; import org.geowebcache.mime.MimeType; import org.geowebcache.storage.CompositeBlobStore.LiveStore; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.mockito.ArgumentMatcher; import com.google.common.base.Throwables; public class CompositeBlobStoreTest { private static final String DEFAULT_GRIDSET = "EPSG:4326"; private static final String DEFAULT_FORMAT = "png"; private static final String DEFAULT_LAYER = "topp:states"; private static class NotEq<T> extends ArgumentMatcher<T> { private T val; public NotEq(T val) { this.val = val; } @Override public boolean matches(Object argument) { return !val.equals(argument); } } @Rule public TemporaryFolder tmpFolder = new TemporaryFolder(); @Rule public ExpectedException ex = ExpectedException.none(); TileLayerDispatcher layers; DefaultStorageFinder defaultStorageFinder; XMLConfiguration configuration; List<BlobStoreConfig> configs; private CompositeBlobStore store; private TileLayer defaultLayer; @Before public void setup() throws Exception { layers = mock(TileLayerDispatcher.class); defaultStorageFinder = mock(DefaultStorageFinder.class); configuration = mock(XMLConfiguration.class); configs = new LinkedList<>(); when(configuration.getBlobStores()).thenReturn(configs); when(defaultStorageFinder.getDefaultPath()).thenReturn( tmpFolder.getRoot().getAbsolutePath()); defaultLayer = mock(TileLayer.class); when(layers.getTileLayer(eq(DEFAULT_LAYER))).thenReturn(defaultLayer); when(layers.getTileLayer((String) argThat(new NotEq<>(DEFAULT_LAYER)))).thenThrow( new GeoWebCacheException("layer not found")); } private CompositeBlobStore create() throws StorageException, ConfigurationException { return new CompositeBlobStore(layers, defaultStorageFinder, configuration); } @Test public void noStoresDefinedCreatesLegacyDefaultStore() throws Exception { store = create(); assertEquals(1, store.blobStores.size()); LiveStore liveStore = store.blobStores.get(CompositeBlobStore.DEFAULT_STORE_DEFAULT_ID); assertNotNull(liveStore); assertTrue(liveStore.config instanceof FileBlobStoreConfig); FileBlobStoreConfig config = (FileBlobStoreConfig) liveStore.config; assertTrue(config.isEnabled()); assertTrue(config.isDefault()); assertEquals(tmpFolder.getRoot().getAbsolutePath(), config.getBaseDirectory()); } @Test public void noExplicitDefaultCreatesLegacyDefaultStore() throws Exception { final boolean isDefault = false; configs.add(config("store1", isDefault, true, tmpFolder.newFolder().getAbsolutePath(), 1024)); configs.add(config("store2", isDefault, true, tmpFolder.newFolder().getAbsolutePath(), 2048)); store = create(); Map<String, LiveStore> stores = store.blobStores; assertEquals(3, stores.size()); LiveStore defaultStore = stores.get(CompositeBlobStore.DEFAULT_STORE_DEFAULT_ID); assertNotNull(defaultStore); assertNotEquals(configs.get(0), defaultStore.config); assertNotEquals(configs.get(1), defaultStore.config); } @Test public void duplicateDefaultStoreFails() throws Exception { final boolean isDefault = true; configs.add(config("store1", isDefault, true, tmpFolder.newFolder().getAbsolutePath(), 1024)); configs.add(config("store2", isDefault, true, tmpFolder.newFolder().getAbsolutePath(), 2048)); ex.expect(ConfigurationException.class); ex.expectMessage("Duplicate default blob store"); store = create(); } @Test public void nullStoreIdFails() throws Exception { String id = null; configs.add(config(id, true, true, tmpFolder.newFolder().getAbsolutePath(), 1024)); ex.expect(ConfigurationException.class); ex.expectMessage("No id provided for blob store"); store = create(); } @Test public void duplicateStoreIdFails() throws Exception { String id = "ImDuplicate"; configs.add(config(id, true, true, tmpFolder.newFolder().getAbsolutePath(), 1024)); configs.add(config(id, true, true, tmpFolder.newFolder().getAbsolutePath(), 1024)); ex.expect(ConfigurationException.class); ex.expectMessage("Duplicate blob store id"); store = create(); } @Test public void defaultAndDisaledFails() throws Exception { boolean isDefault = true; boolean enabled = false; configs.add(config("storeId", isDefault, enabled, tmpFolder.newFolder().getAbsolutePath(), 1024)); ex.expect(ConfigurationException.class); ex.expectMessage("The default blob store can't be disabled"); store = create(); } @Test public void disabledStoreHasNoLiveInstance() throws Exception { boolean enabled = false; configs.add(config("storeId", false, enabled, tmpFolder.newFolder().getAbsolutePath(), 1024)); store = create(); assertNotNull(store.blobStores.get("storeId")); assertNull(store.blobStores.get("storeId").liveInstance); } @Test public void reservedDefaultIdInvalidInConfig() throws Exception { String id = CompositeBlobStore.DEFAULT_STORE_DEFAULT_ID; configs.add(config(id, true, true, tmpFolder.newFolder().getAbsolutePath(), 1024)); ex.expect(ConfigurationException.class); ex.expectMessage(id + " is a reserved identifier"); store = create(); } @Test public void configuredDefaultRespectedAndNoLegacyDefaultCreated() throws Exception { configs.add(config("some-other", false /* isDefault */, true, tmpFolder.newFolder() .getAbsolutePath(), 1024)); FileBlobStoreConfig defaultStore = config("default-store", true, true, tmpFolder .newFolder().getAbsolutePath(), 1024); configs.add(defaultStore); store = create(); // defaultStore is cached twice, with its own id for when layers refers to it explicitly, // and as CompositeBlobStore.DEFAULT_STORE_DEFAULT_ID for layers that do not specify a blob // store assertSame(defaultStore, store.blobStores.get(CompositeBlobStore.DEFAULT_STORE_DEFAULT_ID).config); assertSame(defaultStore, store.blobStores.get("default-store").config); assertEquals(3, store.blobStores.size()); } @Test public void getTileInvalidBlobStore() throws Exception { configs.add(config("default", true, true, tmpFolder.newFolder().getAbsolutePath(), 1024)); store = create(); when(defaultLayer.getBlobStoreId()).thenReturn("nonExistentStore"); ex.expect(StorageException.class); ex.expectMessage("No BlobStore with id 'nonExistentStore' found"); store.get(queryTile(0, 0, 0)); } @Test public void getTileDefaultsToDefaultBlobStore() throws Exception { store = create(); LiveStore liveStore = store.blobStores.get(CompositeBlobStore.DEFAULT_STORE_DEFAULT_ID); liveStore.liveInstance = spy(liveStore.liveInstance); when(defaultLayer.getBlobStoreId()).thenReturn(null); TileObject tile = queryTile(0, 0, 0); store.get(tile); verify(liveStore.liveInstance).get(tile); } @Test public void getTileInvalidLayer() throws Exception { store = create(); LiveStore liveStore = store.blobStores.get(CompositeBlobStore.DEFAULT_STORE_DEFAULT_ID); liveStore.liveInstance = spy(liveStore.liveInstance); when(defaultLayer.getBlobStoreId()).thenReturn(null); TileObject tile = queryTile("someLayer", DEFAULT_GRIDSET, DEFAULT_FORMAT, 0, 0, 0); ex.expect(StorageException.class); ex.expectMessage("layer not found"); store.get(tile); } @Test public void getTileDisabledStore() throws Exception { boolean isEnabled = false; configs.add(config("store1", false, isEnabled, tmpFolder.newFolder().getAbsolutePath(), 1024)); store = create(); when(defaultLayer.getBlobStoreId()).thenReturn("store1"); TileObject tile = queryTile(0, 0, 0); ex.expect(StorageException.class); ex.expectMessage("Attempted to use a blob store that's disabled"); store.get(tile); } private FileBlobStoreConfig config(String id, boolean isDefault, boolean isEnabled, String baseDirectory, int fileSystemBlockSize) { FileBlobStoreConfig c = new FileBlobStoreConfig(id); c.setDefault(isDefault); c.setEnabled(isEnabled); c.setBaseDirectory(baseDirectory); c.setFileSystemBlockSize(fileSystemBlockSize); return c; } private TileObject queryTile(long x, long y, int z) { return queryTile(DEFAULT_LAYER, DEFAULT_GRIDSET, DEFAULT_FORMAT, x, y, z); } private TileObject queryTile(String layer, String gridset, String extension, long x, long y, int z) { return queryTile(layer, gridset, extension, x, y, z, (Map<String, String>) null); } private TileObject queryTile(String layer, String gridset, String extension, long x, long y, int z, Map<String, String> parameters) { String format; try { format = MimeType.createFromExtension(extension).getFormat(); } catch (MimeException e) { throw Throwables.propagate(e); } TileObject tile = TileObject.createQueryTileObject(layer, new long[] { x, y, z }, gridset, format, parameters); return tile; } }