/* (c) 2015 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.jdbcstore;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.*;
import static org.geoserver.platform.resource.ResourceMatchers.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.geoserver.catalog.impl.StyleInfoImpl;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.config.GeoServerDataDirectoryTest;
import org.geoserver.jdbcstore.cache.SimpleResourceCache;
import org.geoserver.jdbcstore.internal.JDBCResourceStoreProperties;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.resource.DataDirectoryResourceStore;
import org.geoserver.platform.resource.FileSystemResourceStore;
import org.geoserver.platform.resource.NullLockProvider;
import org.geoserver.platform.resource.Paths;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.ResourceListener;
import org.geoserver.platform.resource.ResourceNotification;
import org.geoserver.platform.resource.ResourceStore;
import org.geoserver.platform.resource.Resources;
import org.geoserver.util.IOUtils;
import org.geoserver.platform.resource.ResourceNotification.Event;
import org.geoserver.platform.resource.ResourceNotification.Kind;
import org.geotools.styling.ExternalGraphic;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.Style;
import org.geotools.styling.Symbolizer;
import org.geotools.util.Version;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.opengis.style.GraphicalSymbol;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
*
* @author Kevin Smith, Boundless
* @author Niels Charlier
*
*/
public abstract class AbstractJDBCResourceStoreTest {
DatabaseTestSupport support;
@After
public void cleanUp() throws Exception {
support.close();
}
JDBCResourceStoreProperties getConfig(boolean enabled, boolean init) {
JDBCResourceStoreProperties config = createMock(JDBCResourceStoreProperties.class);
expect(config.isInitDb()).andStubReturn(init);
expect(config.isEnabled()).andStubReturn(enabled);
expect(config.isImport()).andStubReturn(init);
expect(config.getIgnoreDirs()).andStubReturn(new String[] {"DirIgnore"});
config.setInitDb(false);
expectLastCall();
try {
config.save();
} catch (Exception e) {}
expectLastCall();
support.stubConfig(config);
replay(config);
return config;
}
@Test
public void testInitializeEmptyDB() throws Exception {
JDBCResourceStoreProperties config = getConfig(true, true);
@SuppressWarnings("unused")
ResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
// Check that the database has a resources table with a root record
ResultSet rs = support.getConnection().createStatement().executeQuery("SELECT * from resources where oid = 0");
assertThat(rs.next(), describedAs("found root record",is(true)));
assertThat(rs.getString("name"), equalTo(""));
rs.getInt("parent");
assertThat(rs.wasNull(), is(true));
assertThat(rs.getBlob("content"), nullValue());
assertThat(rs.next(), describedAs("only one root",is(false)));
}
void standardData() throws Exception {
support.initialize();
support.addFile("FileA", 0, "FileA Contents".getBytes());
support.addFile("FileB", 0, "FileB Contents".getBytes());
int c = support.addDir("DirC", 0);
support.addFile("FileD", c, "FileD Contents".getBytes());
support.addDir("DirE", 0);
int f = support.addDir("DirF", c);
int g = support.addDir("DirG", f);
support.addFile("FileH", g, "FileH Contents".getBytes());
}
@Test
public void testAcceptInitializedDB() throws Exception {
standardData();
JDBCResourceStoreProperties config = getConfig(true, false);
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
store.setLockProvider(new NullLockProvider());
{
// Check that the database has a resources table with a root record
ResultSet rs = support.getConnection().createStatement().executeQuery("SELECT * from resources where oid = 0");
assertThat(rs.next(), describedAs("found root record",is(true)));
assertThat(rs.getString("name"), equalTo(""));
rs.getInt("parent");
assertThat(rs.wasNull(), is(true));
assertThat(rs.getBlob("content"), nullValue());
assertThat(rs.next(), describedAs("only one root",is(false)));
}
{
// Check that the database has one of the child nodes
ResultSet rs = support.getConnection().createStatement().executeQuery("SELECT * from resources where parent = 0 and name='FileA'");
assertThat(rs.next(), describedAs("found child FileA",is(true)));
assertThat(rs.getString("name"), equalTo("FileA"));
assertThat(rs.getInt("parent"), equalTo(0));
assertThat(rs.wasNull(), is(false));
assertThat(rs.getBinaryStream("content"), not(nullValue()));
assertThat(rs.getInt("oid"), not(equalTo(0)));
}
}
@Test
public void testInitializeDatabaseWithIrrelevantTable() throws Exception {
support.getConnection().createStatement().execute("CREATE TABLE foo (oid INTEGER PRIMARY KEY);");
JDBCResourceStoreProperties config = getConfig(true, true);
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
store.setLockProvider(new NullLockProvider());
{
// Check that the database has a resources table with a root record
ResultSet rs = support.getConnection().createStatement().executeQuery("SELECT * from resources where oid = 0");
assertThat(rs.next(), describedAs("found root record",is(true)));
assertThat(rs.getString("name"), equalTo(""));
rs.getInt("parent");
assertThat(rs.wasNull(), is(true));
assertThat(rs.getBlob("content"), nullValue());
assertThat(rs.next(), describedAs("only one root",is(false)));
}
}
@Test
public void testBasicResourceQuery() throws Exception {
standardData();
JDBCResourceStoreProperties config = getConfig(true, false);
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
store.setLockProvider(new NullLockProvider());
Resource r = store.get("FileA");
assertThat(r, not(nullValue()));
assertThat(r, resource());
}
@Test
public void testBasicDirectoryQuery() throws Exception {
standardData();
JDBCResourceStoreProperties config = getConfig(true, false);
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
store.setLockProvider(new NullLockProvider());
Resource r = store.get("DirE");
assertThat(r, not(nullValue()));
assertThat(r, directory());
}
@Test
public void testBasicUndefinedQuery() throws Exception {
standardData();
JDBCResourceStoreProperties config = getConfig(true, false);
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
store.setLockProvider(new NullLockProvider());
Resource r = store.get("DoesntExist");
assertThat(r, not(nullValue()));
assertThat(r, undefined());
}
@Test
public void testLongQuery() throws Exception {
standardData();
JDBCResourceStoreProperties config = getConfig(true, false);
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
store.setLockProvider(new NullLockProvider());
Resource r = store.get("DirC/DirF/DirG/FileH");
assertThat(r, not(nullValue()));
assertThat(r, resource());
}
@Test
public void testBasicRead() throws Exception {
standardData();
JDBCResourceStoreProperties config = getConfig(true, false);
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), config);
store.setLockProvider(new NullLockProvider());
Resource r = store.get("FileA");
byte[] expected = "FileA Contents".getBytes();
InputStream in = r.in();
try {
byte[] result = new byte[expected.length];
assertThat(in.read(result), describedAs("file contents same length",equalTo(expected.length)));
assertThat(result, equalTo(expected));
assertThat(in.read(), describedAs("stream is empty",equalTo(-1)));
} finally {
in.close();
}
}
@Test
public void testCache() throws Exception {
standardData();
cache.create();
JDBCResourceStoreProperties config = getConfig(true, false);
ResourceStore fileStore = new FileSystemResourceStore(cache.getRoot());
ResourceStore jdbcStore = new JDBCResourceStore(support.getDataSource(), config,
fileStore);
((JDBCResourceStore)jdbcStore).setCache(new SimpleResourceCache(cache.getRoot()));
//Initialize FileA in cache
Resource jdbcResource = jdbcStore.get("FileA");
jdbcResource.file();
//Make sure the timestamp is different
Thread.sleep(2);
//Update the Resource in the JDBCStore
byte[] expected = "FileA Updated Contents".getBytes();
OutputStream out = jdbcResource.out();
try {
out.write(expected);
} finally {
out.close();
}
//Force an update to the cache
jdbcResource.file();
//Verify this update actually occurs
Resource fileResource = fileStore.get("FileA");
InputStream in = fileResource.in();
try {
byte[] result = new byte[expected.length];
in.read(result);
assertThat(result, equalTo(expected));
} finally {
in.close();
}
}
@Test
public void testIgnoreDir() throws Exception {
JDBCResourceStoreProperties config = getConfig(true, false);
ResourceStore dataDirStore = new DataDirectoryResourceStore();
ResourceStore store = new JDBCResourceStore(support.getDataSource(), config,
dataDirStore);
assertEquals(store.get("DirIgnore"), dataDirStore.get("DirIgnore"));
assertEquals(store.get("DirIgnore/myfile"), dataDirStore.get("DirIgnore/myfile"));
assertNotEquals(store.get("DirDontIgnore"), dataDirStore.get("DirDontIgnore"));
}
private static class TestResourceListener implements ResourceListener {
private ResourceNotification notify;
@Override
public void changed(ResourceNotification notify) {
this.notify = notify;
}
public ResourceNotification getNotify() {
return notify;
}
public void reset() {
notify = null;
}
}
@Test
public void fileEvents() throws Exception {
standardData();
ResourceStore store = new JDBCResourceStore(support.getDataSource(), getConfig(false, false));
TestResourceListener listener = new TestResourceListener();
Resource fileD = store.get("DirC/FileD");
fileD.addListener(listener);
long before = fileD.lastmodified();
try (OutputStream out = fileD.out()) {
out.write(1234);
}
long after = fileD.lastmodified();
assertTrue(after>before);
assertNotNull(listener.getNotify());
assertEquals(Kind.ENTRY_MODIFY, listener.getNotify().getKind());
assertEquals(1, listener.getNotify().events().size());
long timeStamp = listener.getNotify().getTimestamp();
assertTrue(timeStamp > before);
listener.reset();
fileD.delete();
assertNotNull(listener.getNotify());
assertEquals(Kind.ENTRY_DELETE, listener.getNotify().getKind());
assertEquals(1, listener.getNotify().events().size());
listener.reset();
try (OutputStream out = fileD.out()) {
assertNotNull(listener.getNotify());
assertEquals(Kind.ENTRY_CREATE, listener.getNotify().getKind());
assertEquals(1, listener.getNotify().events().size());
}
fileD.removeListener(listener);
}
@Test
public void directoryEvents() throws Exception {
standardData();
ResourceStore store = new JDBCResourceStore(support.getDataSource(), getConfig(false, false));
Resource fileA = store.get("FileA");
Resource fileD = store.get("DirC/FileD");
TestResourceListener listener = new TestResourceListener();
store.get("DirC").addListener(listener);
long before = fileD.lastmodified();
try (OutputStream out = fileD.out()) {
out.write(1234);
}
long after = fileD.lastmodified();
assertTrue(after>before);
assertNotNull(listener.getNotify());
assertEquals(Kind.ENTRY_MODIFY, listener.getNotify().getKind());
assertEquals(1, listener.getNotify().events().size());
assertEquals("DirC", listener.getNotify().getPath());
long timeStamp = listener.getNotify().getTimestamp();
assertTrue(timeStamp > before);
Event e = listener.getNotify().events().get(0);
assertEquals(Kind.ENTRY_MODIFY, e.getKind());
assertEquals("DirC/FileD", e.getPath());
listener.reset();
store.get("DirC").removeListener(listener);
store.get(Paths.BASE).addListener(listener);
fileA.delete();
assertNotNull(listener.getNotify());
assertEquals(Kind.ENTRY_MODIFY, listener.getNotify().getKind());
assertEquals(1, listener.getNotify().events().size());
assertEquals(Paths.BASE, listener.getNotify().getPath());
e = listener.getNotify().events().get(0);
assertEquals(Kind.ENTRY_DELETE, e.getKind());
assertEquals("FileA", e.getPath());
listener.reset();
try (OutputStream out = fileA.out()) {
assertEquals(Kind.ENTRY_MODIFY, listener.getNotify().getKind());
assertEquals(1, listener.getNotify().events().size());
assertEquals(Paths.BASE, listener.getNotify().getPath());
e = listener.getNotify().events().get(0);
assertEquals(Kind.ENTRY_CREATE, e.getKind());
assertEquals("FileA", e.getPath());
}
store.get(Paths.BASE).removeListener(listener);
}
@Rule
public TemporaryFolder cache = new TemporaryFolder();
@Test
public void testParsedStyle() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"GeoServerDataDirectoryTest-applicationContext.xml", GeoServerDataDirectoryTest.class);
ctx.refresh();
support.initialize();
cache.create();
JDBCResourceStore store = new JDBCResourceStore(support.getDataSource(), getConfig(false, false));
store.setCache(new SimpleResourceCache(cache.getRoot()));
GeoServerResourceLoader loader = new GeoServerResourceLoader(store);
GeoServerDataDirectory dataDir = new GeoServerDataDirectory(loader);
Resource styleDir = store.get("styles");
//Copy the sld to the temp style dir
Resource styleResource = styleDir.get("external.sld");
IOUtils.copy(getClass().getResourceAsStream("external.sld"), styleResource.out());
Resource noIconResource = styleDir.get("noicon.png");
assertFalse(Resources.exists(noIconResource));
Resource iconResource = styleDir.get("icon.png");
IOUtils.copy(getClass().getResourceAsStream("icon.png"), iconResource.out());
assertTrue(Resources.exists(iconResource));
StyleInfoImpl si = new StyleInfoImpl(null);
si.setName("");
si.setId("");
si.setFormat("sld");
si.setFormatVersion(new Version("1.0.0"));
si.setFilename(styleResource.name());
Style s = dataDir.parsedStyle(si);
//Verify style is actually parsed correctly
Symbolizer symbolizer = s.featureTypeStyles().get(0).rules().get(0).symbolizers().get(0);
assertTrue(symbolizer instanceof PointSymbolizer);
GraphicalSymbol graphic = ((PointSymbolizer) symbolizer).getGraphic().graphicalSymbols().get(0);
assertTrue(graphic instanceof ExternalGraphic);
File iconFile = new File(cache.getRoot(), "styles/icon.png");
File noiconFile = new File(cache.getRoot(), "styles/noicon.png");
//GEOS-7025: verify the icon file is not created if it doesn't exist in store
assertFalse(noiconFile.exists());
//GEOS-7741: verify the icon file is created if it does exist in store
assertTrue(iconFile.exists());
ctx.destroy();
ctx.close();
}
}