Skip to content

Commit 06ad555

Browse files
groldanaaime
authored andcommitted
Do not close JNDI-resolved DataSource in JDBCQuotaStore.close()
JNDI lookups return a DataSource owned by the JNDI provider; consumers must not close it, since the same name can be (and typically is) shared with other parts of the application. Add an ownsDataSource flag (default true to keep the previous behavior for direct setDataSource callers) that JDBCQuotaStoreFactory clears whenever the configuration uses a JNDISource. on-behalf-of: @camptocamp <info@camptocamp.com>
1 parent bae5301 commit 06ad555

3 files changed

Lines changed: 90 additions & 5 deletions

File tree

geowebcache/diskquota/jdbc/src/main/java/org/geowebcache/diskquota/jdbc/JDBCQuotaStore.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ public class JDBCQuotaStore implements QuotaStore {
9191

9292
private DataSource dataSource;
9393

94+
/**
95+
* Whether {@link #close()} should close {@link #dataSource}. Set to {@code false} by {@link JDBCQuotaStoreFactory}
96+
* when the DataSource is resolved from JNDI - in that case its lifecycle is owned by the JNDI provider, not by this
97+
* store. Defaults to {@code true} so callers using only {@link #setDataSource(DataSource)} keep the previous
98+
* behavior.
99+
*/
100+
private boolean ownsDataSource = true;
101+
94102
public JDBCQuotaStore(DefaultStorageFinder finder, TilePageCalculator tilePageCalculator) {
95103
this.finder = finder;
96104
this.calculator = tilePageCalculator;
@@ -117,6 +125,24 @@ public void setSchema(String schema) {
117125
this.schema = schema;
118126
}
119127

128+
/**
129+
* @return whether {@link #close()} will close the {@link #setDataSource(DataSource) DataSource}. Defaults to
130+
* {@code true}; set to {@code false} for JNDI-resolved DataSources, whose lifecycle belongs to the JNDI
131+
* provider.
132+
*/
133+
public boolean isOwnsDataSource() {
134+
return ownsDataSource;
135+
}
136+
137+
/**
138+
* Controls whether {@link #close()} will close the configured {@link #setDataSource(DataSource) DataSource}. Set to
139+
* {@code false} when the DataSource is borrowed (typically resolved from JNDI) so this store does not shut down a
140+
* connection pool it does not own.
141+
*/
142+
public void setOwnsDataSource(boolean ownsDataSource) {
143+
this.ownsDataSource = ownsDataSource;
144+
}
145+
120146
/** Sets the connection pool provider and initializes the tables in the dbms if missing */
121147
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
122148
public void setDataSource(DataSource dataSource) {
@@ -652,11 +678,14 @@ public PageStats setTruncated(final TilePage page) throws InterruptedException {
652678
public void close() throws Exception {
653679
log.info("Closing up the JDBC quota store ");
654680

655-
// try to close the data source if possible
656-
if (dataSource instanceof BasicDataSource source) {
657-
source.close();
658-
} else if (dataSource instanceof Closeable closeable) {
659-
closeable.close();
681+
// try to close the data source if possible, but only if we own it - JNDI-resolved
682+
// DataSources are owned by the JNDI provider and must not be closed here.
683+
if (ownsDataSource) {
684+
if (dataSource instanceof BasicDataSource source) {
685+
source.close();
686+
} else if (dataSource instanceof Closeable closeable) {
687+
closeable.close();
688+
}
660689
}
661690

662691
// release the templates

geowebcache/diskquota/jdbc/src/main/java/org/geowebcache/diskquota/jdbc/JDBCQuotaStoreFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ private QuotaStore getJDBCStore(
161161
store.setDialect(dialect);
162162
// sets schema if configured in geowebcache-diskquota-jdbc.xml
163163
store.setSchema(expandedConfig.getSchema());
164+
// JNDI-resolved DataSources are owned by the JNDI provider; inline connection pools are
165+
// created here and owned by the store.
166+
store.setOwnsDataSource(expandedConfig.getJNDISource() == null);
164167

165168
// initialize it
166169
store.initialize();

geowebcache/diskquota/jdbc/src/test/java/org/geowebcache/diskquota/jdbc/JDBCQuotaStoreFactoryTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,19 @@
1414
package org.geowebcache.diskquota.jdbc;
1515

1616
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.assertFalse;
1718
import static org.junit.Assert.assertSame;
19+
import static org.junit.Assert.assertTrue;
1820
import static org.junit.Assert.fail;
1921

22+
import java.io.Closeable;
2023
import java.sql.Connection;
2124
import javax.naming.InitialContext;
2225
import javax.sql.DataSource;
2326
import org.geotools.util.factory.GeoTools;
2427
import org.geowebcache.config.ConfigurationException;
28+
import org.geowebcache.diskquota.storage.TilePageCalculator;
29+
import org.geowebcache.storage.DefaultStorageFinder;
2530
import org.junit.Test;
2631
import org.mockito.Mockito;
2732

@@ -60,4 +65,52 @@ public void testJNDILookup() throws Exception {
6065
GeoTools.clearInitialContext();
6166
}
6267
}
68+
69+
/** A {@link DataSource} that also implements {@link Closeable} so we can observe whether close() is called. */
70+
private interface CloseableDataSource extends DataSource, Closeable {}
71+
72+
@Test
73+
@SuppressWarnings({"PMD.CloseResource"})
74+
public void closeDoesNotCloseJndiDataSource() throws Exception {
75+
CloseableDataSource jndiDs = Mockito.mock(CloseableDataSource.class);
76+
77+
JDBCQuotaStore store =
78+
new JDBCQuotaStore(Mockito.mock(DefaultStorageFinder.class), Mockito.mock(TilePageCalculator.class));
79+
store.setDataSource(jndiDs);
80+
// Mirror the wiring done by JDBCQuotaStoreFactory#getJDBCStore for a JNDI configuration.
81+
store.setOwnsDataSource(false);
82+
83+
store.close();
84+
85+
assertFalse("ownsDataSource flag must be false after explicit set", store.isOwnsDataSource());
86+
Mockito.verify(jndiDs, Mockito.never()).close();
87+
}
88+
89+
@Test
90+
@SuppressWarnings({"PMD.CloseResource"})
91+
public void closeClosesOwnedDataSource() throws Exception {
92+
CloseableDataSource ownedDs = Mockito.mock(CloseableDataSource.class);
93+
94+
JDBCQuotaStore store =
95+
new JDBCQuotaStore(Mockito.mock(DefaultStorageFinder.class), Mockito.mock(TilePageCalculator.class));
96+
store.setDataSource(ownedDs);
97+
// Default ownsDataSource=true preserves the historical behavior.
98+
assertTrue(store.isOwnsDataSource());
99+
100+
store.close();
101+
102+
Mockito.verify(ownedDs).close();
103+
}
104+
105+
@Test
106+
public void closeIsSafeWhenDataSourceLacksCloseable() throws Exception {
107+
DataSource plainDs = Mockito.mock(DataSource.class);
108+
JDBCQuotaStore store =
109+
new JDBCQuotaStore(Mockito.mock(DefaultStorageFinder.class), Mockito.mock(TilePageCalculator.class));
110+
store.setDataSource(plainDs);
111+
112+
// No assertion beyond the absence of an exception - the store should not try to close a
113+
// DataSource that does not implement Closeable / BasicDataSource.
114+
store.close();
115+
}
63116
}

0 commit comments

Comments
 (0)