This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 0bf48cdcae Add an event to be fired when a resource is closed. This
event is handled in a special way, in that it automatically register another
listener on the parent data store for propagating `CloseEvent` to child
resources.
0bf48cdcae is described below
commit 0bf48cdcae1a0c2a6c593e342d9a1ce39ff64501
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Jun 7 19:45:30 2022 +0200
Add an event to be fired when a resource is closed. This event is handled
in a special way, in that it automatically register another listener on the
parent data store for propagating `CloseEvent` to child resources.
https://issues.apache.org/jira/browse/SIS-549
---
.../apache/sis/storage/landsat/LandsatStore.java | 5 +-
.../apache/sis/storage/geotiff/GeoTiffStore.java | 3 +-
.../org/apache/sis/storage/netcdf/NetcdfStore.java | 5 +-
.../apache/sis/storage/netcdf/package-info.java | 2 +-
.../java/org/apache/sis/storage/sql/SQLStore.java | 5 +-
.../org/apache/sis/storage/sql/package-info.java | 2 +-
.../org/apache/sis/internal/storage/Resources.java | 5 +
.../sis/internal/storage/Resources.properties | 1 +
.../sis/internal/storage/Resources_fr.properties | 1 +
.../org/apache/sis/internal/storage/csv/Store.java | 5 +-
.../sis/internal/storage/csv/package-info.java | 2 +-
.../sis/internal/storage/esri/AsciiGridStore.java | 3 +-
.../sis/internal/storage/esri/RasterStore.java | 3 +-
.../sis/internal/storage/esri/RawRasterStore.java | 3 +-
.../sis/internal/storage/esri/WritableStore.java | 3 +-
.../sis/internal/storage/esri/package-info.java | 2 +-
.../apache/sis/internal/storage/folder/Store.java | 5 +-
.../sis/internal/storage/folder/package-info.java | 2 +-
.../sis/internal/storage/image/WorldFileStore.java | 5 +-
.../sis/internal/storage/image/WritableStore.java | 3 +-
.../sis/internal/storage/image/package-info.java | 2 +-
.../org/apache/sis/internal/storage/wkt/Store.java | 5 +-
.../sis/internal/storage/wkt/package-info.java | 2 +-
.../org/apache/sis/internal/storage/xml/Store.java | 5 +-
.../sis/internal/storage/xml/package-info.java | 2 +-
.../java/org/apache/sis/storage/DataStore.java | 18 +-
.../org/apache/sis/storage/event/CloseEvent.java | 87 +++++++++
.../org/apache/sis/storage/event/StoreEvent.java | 47 ++++-
.../apache/sis/storage/event/StoreListeners.java | 203 ++++++++++++++++++---
.../java/org/apache/sis/storage/DataStoreMock.java | 15 +-
.../sis/storage/event/StoreListenersTest.java | 35 +++-
.../org/apache/sis/internal/storage/gpx/Store.java | 5 +-
.../sis/internal/storage/gpx/package-info.java | 2 +-
.../internal/storage/xml/stream/StaxDataStore.java | 4 +
34 files changed, 426 insertions(+), 71 deletions(-)
diff --git
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
index 30fa5eb49a..1786e17eda 100644
---
a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
+++
b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/LandsatStore.java
@@ -79,7 +79,7 @@ import org.apache.sis.setup.OptionKey;
*
* @author Thi Phuong Hao Nguyen (VNSC)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -153,7 +153,7 @@ public class LandsatStore extends DataStore implements
Aggregate {
connector.getStorage(),
connector.getOption(OptionKey.OPEN_OPTIONS));
}
if (getClass() == LandsatStore.class) {
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
}
@@ -294,6 +294,7 @@ public class LandsatStore extends DataStore implements
Aggregate {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
metadata = null;
DataStoreException error = null;
for (final Band band : BandGroup.bands(components)) {
diff --git
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
index 859cb69e3d..90c53e20c6 100644
---
a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
+++
b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
@@ -209,7 +209,7 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
throw new DataStoreException(e);
}
if (getClass() == GeoTiffStore.class) {
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
}
@@ -511,6 +511,7 @@ public class GeoTiffStore extends DataStore implements
Aggregate {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final Reader r = reader;
reader = null;
components = null;
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
index 407aa5569f..3d1a5151c2 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
@@ -58,7 +58,7 @@ import ucar.nc2.constants.CDM;
* Instances of this data store are created by {@link
NetcdfStoreProvider#open(StorageConnector)}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
*
* @see NetcdfStoreProvider
*
@@ -129,7 +129,7 @@ public class NetcdfStore extends DataStore implements
Aggregate {
decoder.namespace = f.createNameSpace(f.createLocalName(null, id),
null);
}
if (getClass() == NetcdfStore.class) {
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
}
@@ -267,6 +267,7 @@ public class NetcdfStore extends DataStore implements
Aggregate {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final Decoder reader = decoder;
decoder = null;
metadata = null;
diff --git
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java
index 2955c7220d..547a5dc248 100644
---
a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java
+++
b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/package-info.java
@@ -40,7 +40,7 @@
* Care must be taken for avoiding confusion when using SIS and UCAR libraries
together.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 0.3
* @module
*/
diff --git
a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
index 06566785c9..c2fc534734 100644
---
a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
+++
b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
@@ -56,7 +56,7 @@ import org.apache.sis.util.Exceptions;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.0
* @module
*/
@@ -163,7 +163,7 @@ public class SQLStore extends DataStore implements
Aggregate {
this.tableNames = ArraysExt.resize(tableNames, tableCount);
this.queries = ArraysExt.resize(queries, queryCount);
if (getClass() == SQLStore.class) {
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
}
@@ -332,6 +332,7 @@ public class SQLStore extends DataStore implements
Aggregate {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
// There is no JDBC connection to close here.
model = null;
}
diff --git
a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
index 5fab0666d4..89cdda1084 100644
---
a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
+++
b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/package-info.java
@@ -56,7 +56,7 @@
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.0
* @module
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
index d27cddc3e1..39b136756a 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
@@ -214,6 +214,11 @@ public final class Resources extends IndexedResourceBundle
{
*/
public static final short DuplicatedSampleDimensionIndex_1 = 53;
+ /**
+ * Exception occurred in a listener for events of type ‘{0}’.
+ */
+ public static final short ExceptionInListener_1 = 74;
+
/**
* Header in the “{0}” file is too large.
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
index cd6a783721..0f863cd896 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
@@ -65,6 +65,7 @@ IncompatibleGridGeometry = All coverages must have
the same grid geomet
InconsistentNameComponents_2 = Components of the \u201c{1}\u201d name are
inconsistent with those of the name previously binded in \u201c{0}\u201d data
store.
InvalidExpression_2 = Invalid or unsupported \u201c{1}\u201d
expression at index {0}.
InvalidSampleDimensionIndex_2 = Sample dimension index {1} is invalid.
Expected an index from 0 to {0} inclusive.
+ExceptionInListener_1 = Exception occurred in a listener for
events of type \u2018{0}\u2019.
LoadedGridCoverage_6 = Loaded grid coverage between {1} \u2013
{2} and {3} \u2013 {4} from file \u201c{0}\u201d in {5} seconds.
MarkNotSupported_1 = Marks are not supported on \u201c{0}\u201d
stream.
MissingResourceIdentifier_1 = Resource \u201c{0}\u201d does not have an
identifier.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
index 6f87021ddc..3a5d43e79b 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
@@ -70,6 +70,7 @@ IncompatibleGridGeometry = Toutes les couvertures de
donn\u00e9es doive
InvalidExpression_2 = Expression \u00ab\u202f{1}\u202f\u00bb
invalide ou non-support\u00e9e \u00e0 l\u2019index {0}.
InvalidSampleDimensionIndex_2 = L\u2019index de dimension
d\u2019\u00e9chantillonnage {1} est invalide. On attendait un index de 0 \u00e0
{0} inclusif.
InconsistentNameComponents_2 = Les \u00e9l\u00e9ments qui composent le
nom \u00ab\u202f{1}\u202f\u00bb ne sont pas coh\u00e9rents avec ceux du nom qui
avait \u00e9t\u00e9 pr\u00e9c\u00e9demment li\u00e9 dans les donn\u00e9es de
\u00ab\u202f{0}\u202f\u00bb.
+ExceptionInListener_1 = Une exception est survenue dans un capteur
d'\u00e9v\u00e9nements de type \u2018{0}\u2019.
LoadedGridCoverage_6 = Lecture d\u2019une couverture de
donn\u00e9es entre {1} \u2013 {2} et {3} \u2013 {4} \u00e0 partir du fichier
\u00ab\u202f{0}\u202f\u00bb en {5} secondes.
MarkNotSupported_1 = Les marques ne sont pas support\u00e9es
sur le flux \u00ab\u202f{0}\u202f\u00bb.
MissingResourceIdentifier_1 = La ressource \u00ab\u202f{0}\u202f\u00bb
n\u2019a pas d\u2019identifiant.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
index 50fd6de7b7..084d67ffa7 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
@@ -305,7 +305,7 @@ final class Store extends URIDataStore implements
FeatureSet {
this.featureType = featureType;
this.foliation = foliation;
this.dissociate |= (timeEncoding == null);
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
/**
@@ -846,8 +846,9 @@ final class Store extends URIDataStore implements
FeatureSet {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final BufferedReader s = source;
- source = null; // Cleared first in case of failure.
+ source = null; // Cleared first in case of
failure.
if (s != null) try {
s.close();
} catch (IOException e) {
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
index 3e75069761..a9fb532ee4 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
@@ -53,7 +53,7 @@
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 0.7
* @module
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
index 26266fd599..0980ae4a0f 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/AsciiGridStore.java
@@ -140,7 +140,7 @@ import org.apache.sis.util.resources.Errors;
* which is usually the case given how inefficient this format is.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.2
* @module
*/
@@ -544,6 +544,7 @@ cellsize: if (value != null) {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final CharactersView view = input;
input = null; // Cleared first in case of
failure.
coverage = null;
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
index 0e588da647..5dcda27a12 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RasterStore.java
@@ -133,7 +133,7 @@ abstract class RasterStore extends PRJDataStore implements
GridCoverageResource
RasterStore(final DataStoreProvider provider, final StorageConnector
connector) throws DataStoreException {
super(provider, connector);
nodataValue = Double.NaN;
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
/**
@@ -515,6 +515,7 @@ abstract class RasterStore extends PRJDataStore implements
GridCoverageResource
*/
@Override
public void close() throws DataStoreException {
+ // `listeners.close()` should be invoked by sub-classes.
metadata = null;
}
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
index adef8032fa..41006b2a3a 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/RawRasterStore.java
@@ -60,7 +60,7 @@ import static org.apache.sis.internal.util.Numerics.wholeDiv;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.2
* @module
*/
@@ -531,6 +531,7 @@ final class RawRasterStore extends RasterStore {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final ChannelDataInput in = input;
input = null; // Cleared first in case of
failure.
reader = null;
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
index 1b51c553c2..6f10d6c2ea 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/WritableStore.java
@@ -49,7 +49,7 @@ import org.opengis.coverage.grid.SequenceType;
* An ASCII Grid store with writing capabilities.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.2
* @module
*/
@@ -295,6 +295,7 @@ final class WritableStore extends AsciiGridStore implements
WritableGridCoverage
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final ChannelDataOutput out = output;
output = null;
if (out != null) try {
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
index 1746e7c191..31e37c1273 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/esri/package-info.java
@@ -46,7 +46,7 @@
* Sub-setting parameters are ignored.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
*
* @see <a
href="https://desktop.arcgis.com/en/arcmap/latest/manage-data/raster-and-images/esri-ascii-raster-format.htm">Esri
ASCII raster format</a>
* @see <a
href="https://desktop.arcgis.com/en/arcmap/latest/manage-data/raster-and-images/bil-bip-and-bsq-raster-files.htm">BIL,
BIP, and BSQ raster files</a>
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
index d7b5ae5817..22c788df69 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/Store.java
@@ -78,7 +78,7 @@ import org.apache.sis.storage.event.WarningEvent;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 0.8
* @module
*/
@@ -178,7 +178,7 @@ class Store extends DataStore implements StoreResource,
Aggregate, DirectoryStre
children = new ConcurrentHashMap<>();
children.put(path.toRealPath(), this);
componentProvider = format;
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
/**
@@ -431,6 +431,7 @@ class Store extends DataStore implements StoreResource,
Aggregate, DirectoryStre
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should
never fail.
final Collection<Resource> resources = components;
if (resources != null) {
components = null; // Clear
first in case of failure.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java
index 906c57fc3c..c93d40ac46 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/folder/package-info.java
@@ -19,7 +19,7 @@
* {@link org.apache.sis.storage.DataStore} implementation for a folder
containing an arbitrary amount of data files.
*
* @author Johann Sorel (Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 0.8
* @module
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
index 2099f5e115..70c73afc46 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WorldFileStore.java
@@ -110,7 +110,7 @@ import org.apache.sis.setup.OptionKey;
* is known to support only one image per file.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.2
* @module
*/
@@ -239,7 +239,7 @@ public class WorldFileStore extends PRJDataStore {
*/
WorldFileStore(final FormatFinder format, final boolean readOnly) throws
DataStoreException, IOException {
super(format.provider, format.connector);
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
identifiers = new HashMap<>();
suffix = format.suffix;
if (readOnly || !format.openAsWriter) {
@@ -782,6 +782,7 @@ loop: for (int convention=0;; convention++) {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final ImageReader codec = reader;
reader = null;
metadata = null;
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
index 18136c3de7..cb7956c0eb 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/WritableStore.java
@@ -74,7 +74,7 @@ import org.apache.sis.setup.OptionKey;
* known to support only one image per file.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.2
* @module
*/
@@ -494,6 +494,7 @@ writeCoeffs: for (int i=0;; i++) {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
try {
final ImageWriter codec = writer;
writer = null;
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/package-info.java
index d5768d82dd..b98b6eb255 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/package-info.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/package-info.java
@@ -40,7 +40,7 @@
* then move in a public package with {@code imageio} package name.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
*
* @see <a href="https://en.wikipedia.org/wiki/World_file">World File format
description on Wikipedia</a>
*
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
index dc15d878b0..947698ccf8 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
@@ -47,7 +47,7 @@ import org.apache.sis.util.CharSequences;
* the file containing WKT definition is the main file, not an auxiliary
file.</div>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 0.7
* @module
*/
@@ -106,7 +106,7 @@ final class Store extends URIDataStore {
timezone = connector.getOption(OptionKey.TIMEZONE);
library = connector.getOption(OptionKey.GEOMETRY_LIBRARY);
source = connector.commit(Reader.class, StoreProvider.NAME);
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
/**
@@ -199,6 +199,7 @@ final class Store extends URIDataStore {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final Reader s = source;
source = null; // Cleared first in case of failure.
objects.clear();
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
index 21ef05a1cd..db340993be 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/package-info.java
@@ -19,7 +19,7 @@
* {@link org.apache.sis.storage.DataStore} implementation for Well Known Text.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 0.7
* @module
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
index d4ec9e17da..d37630bb1e 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
@@ -57,7 +57,7 @@ import org.apache.sis.setup.OptionKey;
* The above list may be extended in any future SIS version.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.3
* @since 0.4
* @module
*/
@@ -102,7 +102,7 @@ final class Store extends URIDataStore implements Filter {
throw new UnsupportedStorageException(super.getLocale(),
StoreProvider.NAME,
connector.getStorage(),
connector.getOption(OptionKey.OPEN_OPTIONS));
}
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
/**
@@ -224,6 +224,7 @@ final class Store extends URIDataStore implements Filter {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
object = null;
final Closeable in = input(source);
source = null; // Cleared first in case of
failure.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/package-info.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/package-info.java
index dbd4be634c..2f1691aa47 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/package-info.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/package-info.java
@@ -26,7 +26,7 @@
* the {@code sis-xmlstore} module extends this package with classes designed
for use with StAX cursor API.</p>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.3
* @since 0.4
* @module
*/
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
index 5731e5f275..5b8f2e8801 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
@@ -35,6 +35,7 @@ import org.apache.sis.internal.storage.StoreUtilities;
import org.apache.sis.internal.storage.Resources;
import org.apache.sis.internal.util.Strings;
import org.apache.sis.referencing.NamedIdentifier;
+import org.apache.sis.storage.event.CloseEvent;
import org.apache.sis.storage.event.StoreEvent;
import org.apache.sis.storage.event.StoreListener;
import org.apache.sis.storage.event.StoreListeners;
@@ -526,19 +527,26 @@ public abstract class DataStore implements Resource,
Localized, AutoCloseable {
/**
* Closes this data store and releases any underlying resources.
+ * A {@link CloseEvent} is sent to listeners before the data store is
closed.
*
* <h4>Note for implementers</h4>
- * Data stores having resources to release should <em>not</em> override
the {@link Object#equals(Object)}
- * and {@link #hashCode()} methods, since comparisons other than identity
comparisons may confuse some
- * cache mechanisms (e.g. they may think that a data store has already
been closed).
- * Conversely data stores for which {@code addListener(…)}, {@code
removeListener(…)} and {@code close()}
- * methods perform no operation can override {@code equals(…)} and {@code
hashCode()} if desired.
+ * Implementations should invoke {@code listeners.close()} on their first
line
+ * for sending notification to all listeners before the data store is
actually
+ * closed.
*
* @throws DataStoreException if an error occurred while closing this data
store.
+ *
+ * @see StoreListeners#close()
*/
@Override
public abstract void close() throws DataStoreException;
+ /*
+ * Data stores should not override `Object.equals(Object)` and
`hashCode()` methods,
+ * because comparisons other than identity comparisons may confuse cache
mechanisms
+ * (e.g. caches may think that a data store has already been closed).
+ */
+
/**
* Returns a string representation of this data store for debugging
purpose.
* The content of the string returned by this method may change in any
future SIS version.
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/CloseEvent.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/CloseEvent.java
new file mode 100644
index 0000000000..4ca37e07a2
--- /dev/null
+++
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/CloseEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.event;
+
+import org.apache.sis.storage.Resource;
+
+
+/**
+ * Notifies listeners that a resource or a data store is being closed and
should no longer be used.
+ * Resources are automatically considered closed when a parent resource or
data store is closed.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 1.3
+ * @version 1.3
+ * @module
+ */
+public class CloseEvent extends StoreEvent {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 9121559491613566295L;
+
+ /**
+ * Constructs an event for a resource that has been closed.
+ *
+ * @param source the resource which has been closed.
+ * @throws IllegalArgumentException if the given source is null.
+ */
+ public CloseEvent(final Resource source) {
+ super(source);
+ }
+
+
+
+
+ /**
+ * A listener to register on the parent of a resource for closing the
resource
+ * automatically if the parent is closed.
+ *
+ * @see StoreListeners#closeListener
+ */
+ static final class ParentListener implements StoreListener<CloseEvent> {
+ /**
+ * The parent resource to listen to.
+ */
+ private final Resource parent;
+
+ /**
+ * The listeners to notify.
+ */
+ private final StoreListeners listeners;
+
+ /**
+ * Creates a new listener to be registered on the parent of the given
set of listeners.
+ *
+ * @param parent the parent resource to listen to.
+ * @param listeners the child set of listeners.
+ */
+ ParentListener(final Resource parent, final StoreListeners listeners) {
+ this.parent = parent;
+ this.listeners = listeners;
+ }
+
+ /**
+ * Invoked when a parent resource or data store is closed.
+ */
+ @Override public void eventOccured(final CloseEvent event) {
+ if (event.getSource() == parent) { // Necessary check for
avoiding never-ending loop.
+ listeners.close();
+ }
+ }
+ }
+}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java
index 7b6603f5bb..a7f50abfd1 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreEvent.java
@@ -30,7 +30,7 @@ import org.apache.sis.internal.storage.StoreResource;
* Those events are created by {@link Resource} implementations and sent to
all registered listeners.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.3
*
* @see StoreListener
*
@@ -43,6 +43,18 @@ public abstract class StoreEvent extends EventObject
implements Localized {
*/
private static final long serialVersionUID = -1725093072445990248L;
+ /**
+ * Whether this event has been consumed.
+ * A consumed event is not propagated to other listeners.
+ */
+ private boolean consumed;
+
+ /**
+ * Whether to consume this event after all listeners registered on the
{@linkplain #getSource() source}
+ * resource but before listeners registered on the parent resource or data
store.
+ */
+ private boolean consumeLater;
+
/**
* Constructs an event that occurred in the given resource.
*
@@ -88,4 +100,37 @@ public abstract class StoreEvent extends EventObject
implements Localized {
}
return null;
}
+
+ /**
+ * Indicates whether this event has been consumed by any listener.
+ * A consumed event is not propagated further to other listeners.
+ *
+ * @return {@code true} if this event has been consumed, {@code false}
otherwise.
+ *
+ * @since 1.3
+ */
+ public final boolean isConsumed() {
+ return consumed;
+ }
+
+ /**
+ * Returns {@code true} if the event propagation can continue with parent
listeners.
+ */
+ final boolean isConsumedForParent() {
+ return consumed |= consumeLater;
+ }
+
+ /**
+ * Marks this event as consumed. This stops its further propagation to
other listeners.
+ *
+ * @param later {@code true} for consuming now, or {@code false} for
consuming after all listeners
+ * registered on the {@linkplain #getSource() source} resource but
before listeners registered
+ * on the parent resource or data store.
+ *
+ * @since 1.3
+ */
+ public void consume(final boolean later) {
+ if (later) consumed = true;
+ else consumeLater = true;
+ }
}
diff --git
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
index 1ec498eaf7..924d1f66cd 100644
---
a/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
+++
b/storage/sis-storage/src/main/java/org/apache/sis/storage/event/StoreListeners.java
@@ -21,12 +21,12 @@ import java.util.Set;
import java.util.HashSet;
import java.util.Locale;
import java.util.Optional;
-import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import java.util.logging.Filter;
+import java.util.concurrent.ExecutionException;
import java.lang.reflect.Method;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Localized;
@@ -35,6 +35,8 @@ import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.collection.Containers;
+import org.apache.sis.internal.jdk9.JDK9;
+import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.storage.Resources;
import org.apache.sis.internal.storage.StoreResource;
import org.apache.sis.internal.storage.StoreUtilities;
@@ -114,8 +116,19 @@ public class StoreListeners implements Localized {
/**
* Frequently used value for {@link #permittedEventTypes}.
+ *
+ * @see #useReadOnlyEvents()
*/
- private static final Set<Class<? extends StoreEvent>> WARNING_EVENT_TYPE =
Collections.singleton(WarningEvent.class);
+ private static final Set<Class<? extends StoreEvent>> READ_EVENT_TYPES =
+ JDK9.setOf(WarningEvent.class, CloseEvent.class);
+
+ /**
+ * The {@link CloseEvent.ParentListener} registered on {@link #parent}, or
{@code null} if not yet created.
+ * This is created the first time that a {@link CloseEvent} listener is
registered on a resource which is
+ * not the root resource. Those listeners are handled in a special way,
because a close event on the root
+ * should propagate to all children.
+ */
+ private StoreListener<CloseEvent> closeListener;
/**
* All listeners for a given even type.
@@ -177,8 +190,11 @@ public class StoreListeners implements Localized {
* It the listener has been registered twice, only the most recent
registration is removed.
*
* <p>It is caller responsibility to perform synchronization.</p>
+ *
+ * @param listener the listener to remove.
+ * @return {@code true} if the list of listeners is empty after this
method call.
*/
- final void remove(final StoreListener<? super T> listener) {
+ final boolean remove(final StoreListener<? super T> listener) {
StoreListener<? super T>[] list = listeners;
if (list != null) {
for (int i=list.length; --i >= 0;) {
@@ -193,6 +209,7 @@ public class StoreListeners implements Localized {
}
}
}
+ return list == null;
}
/**
@@ -224,19 +241,30 @@ public class StoreListeners implements Localized {
* @param event the event to send to listeners.
* @param done listeners who were already notified, for avoiding to
notify them twice.
* @return the {@code done} map, created when first needed.
+ * @throws ExecutionException if at least one listener failed to
execute.
*/
- final Map<StoreListener<?>,Boolean> eventOccured(final T event,
Map<StoreListener<?>,Boolean> done) {
+ final Map<StoreListener<?>,Boolean> eventOccured(final T event,
Map<StoreListener<?>,Boolean> done)
+ throws ExecutionException
+ {
+ RuntimeException error = null;
final StoreListener<? super T>[] list = listeners;
if (list != null) {
if (done == null) {
done = new IdentityHashMap<>(list.length);
}
for (final StoreListener<? super T> listener : list) {
- if (done.put(listener, Boolean.TRUE) == null) {
+ if (event.isConsumed()) break;
+ if (done.put(listener, Boolean.TRUE) == null) try {
listener.eventOccured(event);
+ } catch (RuntimeException ex) {
+ if (error == null) error = ex;
+ else error.addSuppressed(ex);
}
}
}
+ if (error != null) {
+ throw new
ExecutionException(Resources.format(Resources.Keys.ExceptionInListener_1,
type), error);
+ }
return done;
}
}
@@ -293,13 +321,13 @@ public class StoreListeners implements Localized {
private static DataStore getDataStore(StoreListeners m) {
do {
final Resource source = m.source;
- if (source instanceof DataStore) {
- return (DataStore) source;
- }
if (source instanceof StoreResource) {
final DataStore ds = ((StoreResource) source).getOriginator();
if (ds != null) return ds;
}
+ if (source instanceof DataStore) { // Fallback if not
explicitly specified.
+ return (DataStore) source;
+ }
m = m.parent;
} while (m != null);
return null;
@@ -384,23 +412,13 @@ public class StoreListeners implements Localized {
}
/**
- * Notifies this {@code StoreListeners} that it will fire only {@link
WarningEvent}s. This method is a
- * shortcut for <code>{@linkplain setUsableEventTypes
setUsableEventTypes}(WarningEvent.class)}</code>,
- * provided because frequently used by read-only data store
implementations.
- *
- * @see #setUsableEventTypes(Class...)
- * @see WarningEvent
+ * @deprecated Renamed {@link #useReadOnlyEvents()}.
*
* @since 1.2
*/
- public synchronized void useWarningEventsOnly() {
- final Set<Class<? extends StoreEvent>> current = permittedEventTypes;
- if (current == null) {
- permittedEventTypes = WARNING_EVENT_TYPE;
- } else if (!WARNING_EVENT_TYPE.equals(current)) {
- throw illegalEventType(WarningEvent.class);
- }
- ForType.removeUnreachables(listeners, WARNING_EVENT_TYPE);
+ @Deprecated
+ public void useWarningEventsOnly() {
+ useReadOnlyEvents();
}
/**
@@ -573,20 +591,43 @@ public class StoreListeners implements Localized {
}
}
+ /**
+ * Invoked if an error occurred in a least one listener during the
propagation of an event.
+ * The {@linkplain ExecutionException#getCause() cause} of the exception
is a {@link RuntimeException}.
+ * If exceptions occurred in more than one listener, all exceptions after
the first one are specified
+ * as {@linkplain ExecutionException#getSuppressed() suppressed
exceptions} of the cause.
+ *
+ * <p>This method should not delegate to {@link #warning(Exception)}
because the error is not with the
+ * data store itself. Furthermore the exception may have occurred during
{@code warning(…)} execution,
+ * in which case the exception is a kind of "warning about warning
report".</p>
+ *
+ * @param method name of the method invoking this method.
+ * @param error the exception that occurred.
+ */
+ private static void canNotNotify(final String method, final
ExecutionException error) {
+ Logging.unexpectedException(Logger.getLogger(Modules.STORAGE),
StoreListeners.class, method, error);
+ }
+
/**
* Sends the given event to all listeners registered for the given type or
for a super-type.
* This method first notifies the listeners registered in this {@code
StoreListeners}, then
* notifies listeners registered in parent {@code StoreListeners}s. Each
listener will be
* notified only once even if it has been registered many times.
*
+ * <p>If one or many {@link StoreListener#eventOccured(StoreEvent)}
implemetations throw a
+ * {@link RuntimeException}, those exceptions will be collected and
reported in a single
+ * {@linkplain Logging#unexpectedException(Logger, Class, String,
Throwable) log record}.
+ * Runtime exceptions in listeners do not cause this method to fail.</p>
+ *
* @param <T> compile-time value of the {@code eventType} argument.
* @param event the event to fire.
- * @param eventType the type of events to be fired.
+ * @param eventType the type of the event to be fired.
* @return {@code true} if the event has been sent to at least one
listener.
* @throws IllegalArgumentException if the given event type is not one of
the types of events
* that this {@code StoreListeners} can fire.
+ *
+ * @see #close()
*/
- @SuppressWarnings("unchecked")
public <T extends StoreEvent> boolean fire(final T event, final Class<T>
eventType) {
ArgumentChecks.ensureNonNull("event", event);
ArgumentChecks.ensureNonNull("eventType", eventType);
@@ -594,16 +635,48 @@ public class StoreListeners implements Localized {
if (permittedEventTypes != null &&
!permittedEventTypes.contains(eventType)) {
throw illegalEventType(eventType);
}
+ try {
+ return fire(this, event, eventType);
+ } catch (ExecutionException ex) {
+ canNotNotify("fire", ex);
+ return true;
+ }
+ }
+
+ /**
+ * Sends the given event to all listeners registered in the given set of
listeners and its parent.
+ * This method does not perform any argument validation; they must be done
by the caller.
+ *
+ * <p>This method does not need (and should not) be synchronized.</p>
+ *
+ * @param <T> compile-time value of the {@code eventType} argument.
+ * @param m the set of listeners that may be interested in the
event.
+ * @param event the event to fire.
+ * @param eventType the type of the event to be fired.
+ * @return {@code true} if the event has been sent to at least one
listener.
+ * @throws ExecutionException
+ */
+ @SuppressWarnings("unchecked")
+ private static <T extends StoreEvent> boolean fire(StoreListeners m, final
T event, final Class<T> eventType)
+ throws ExecutionException
+ {
Map<StoreListener<?>,Boolean> done = null;
- StoreListeners m = this;
+ ExecutionException error = null;
do {
for (ForType<?> e = m.listeners; e != null; e = e.next) {
- if (e.type.isAssignableFrom(eventType)) {
+ if (e.type.isAssignableFrom(eventType)) try {
done = ((ForType<? super T>) e).eventOccured(event, done);
+ } catch (ExecutionException ex) {
+ if (error == null) error = ex;
+ else error.getCause().addSuppressed(ex.getCause());
}
+ if (event.isConsumedForParent()) break;
}
m = m.parent;
} while (m != null);
+ if (error != null) {
+ throw error;
+ }
return (done != null) && !done.isEmpty();
}
@@ -672,6 +745,16 @@ public class StoreListeners implements Localized {
listeners = ce;
}
ce.add(listener);
+ /*
+ * If we are adding a listener for `CloseEvent`, we may need (as a
special case)
+ * to register a listener to the parent for propagating the close
events.
+ */
+ if (parent != null) {
+ if (closeListener == null &&
CloseEvent.class.isAssignableFrom(eventType)) {
+ closeListener = new
CloseEvent.ParentListener(parent.source, this);
+ parent.addListener(CloseEvent.class, closeListener);
+ }
+ }
}
}
@@ -703,7 +786,17 @@ public class StoreListeners implements Localized {
ArgumentChecks.ensureNonNull("eventType", eventType);
for (ForType<?> e = listeners; e != null; e = e.next) {
if (e.type.equals(eventType)) {
- ((ForType<T>) e).remove(listener);
+ if (((ForType<T>) e).remove(listener) && parent != null) {
+ /*
+ * If the list of listeners become empty and if the event
type was `CloseEvent`,
+ * cleanup the parent list of listeners too. We do a
special case for close events
+ * because closing a parent data store implicitly closes
the child resources.
+ */
+ if (closeListener != null &&
CloseEvent.class.isAssignableFrom(eventType)) {
+ parent.removeListener(CloseEvent.class, closeListener);
+ closeListener = null;
+ }
+ }
break;
}
}
@@ -760,7 +853,7 @@ public class StoreListeners implements Localized {
* @param permitted type of events that are permitted. Permitted
sub-types shall be explicitly enumerated as well.
* @throws IllegalArgumentException if one of the given types was not
permitted before invocation of this method.
*
- * @see #useWarningEventsOnly()
+ * @see #useReadOnlyEvents()
*
* @since 1.2
*/
@@ -776,7 +869,59 @@ public class StoreListeners implements Localized {
throw illegalEventType(type);
}
}
- permittedEventTypes = WARNING_EVENT_TYPE.equals(types) ?
WARNING_EVENT_TYPE : CollectionsExt.compact(types);
+ permittedEventTypes = READ_EVENT_TYPES.equals(types) ?
READ_EVENT_TYPES : CollectionsExt.compact(types);
ForType.removeUnreachables(listeners, types);
}
+
+ /**
+ * Notifies this {@code StoreListeners} that it will fire only {@link
WarningEvent}s and {@link CloseEvent}.
+ * This method is a shortcut for <code>{@linkplain setUsableEventTypes
setUsableEventTypes}(WarningEvent.class,
+ * CloseEvent.class)}</code>, provided because frequently used by
read-only data store implementations.
+ *
+ * <p>Declaring a root resource (typically a {@link DataStore}) as
read-only implies that all children
+ * (e.g. {@linkplain org.apache.sis.storage.Aggregate#components()
components of an aggregate})
+ * are also read-only.</p>
+ *
+ * @see #setUsableEventTypes(Class...)
+ * @see WarningEvent
+ * @see CloseEvent
+ *
+ * @since 1.3
+ */
+ public synchronized void useReadOnlyEvents() {
+ final Set<Class<? extends StoreEvent>> current = permittedEventTypes;
+ if (current == null) {
+ permittedEventTypes = READ_EVENT_TYPES;
+ } else if (!READ_EVENT_TYPES.equals(current)) {
+ throw illegalEventType(WarningEvent.class);
+ }
+ ForType.removeUnreachables(listeners, READ_EVENT_TYPES);
+ }
+
+ /**
+ * Sends a {@link CloseEvent} to all listeners registered for that kind of
event,
+ * then discards listeners in this instance (but not in parents).
+ * Because listeners are discarded, invoking this method many times
+ * on the same instance have no effect after the first invocation.
+ *
+ * <p>If one or many {@link StoreListener#eventOccured(StoreEvent)}
implementations throw
+ * a {@link RuntimeException}, those exceptions will be collected and
reported in a single
+ * {@linkplain Logging#unexpectedException(Logger, Class, String,
Throwable) log record}.
+ * Runtime exceptions in listeners do not cause this method to fail.</p>
+ *
+ * @see #fire(StoreEvent, Class)
+ * @see DataStore#close()
+ *
+ * @since 1.3
+ */
+ public void close() {
+ try {
+ fire(this, new CloseEvent(source), CloseEvent.class);
+ } catch (ExecutionException ex) {
+ canNotNotify("close", ex);
+ }
+ closeListener = null;
+ listeners = null; // Volatile field should be last.
+ // Do not remove parent listeners; maybe parent will be closed next.
+ }
}
diff --git
a/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java
b/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java
index a51a921ced..ea41037b92 100644
---
a/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java
+++
b/storage/sis-storage/src/test/java/org/apache/sis/storage/DataStoreMock.java
@@ -26,7 +26,7 @@ import org.apache.sis.storage.event.StoreListeners;
* A dummy data store
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.3
* @since 0.8
* @module
*/
@@ -70,10 +70,11 @@ public final strictfp class DataStoreMock extends DataStore
{
}
/**
- * Do nothing.
+ * Notifies listeners if any. Otherwise does nothing.
*/
@Override
public void close() {
+ listeners.close();
}
/**
@@ -95,4 +96,14 @@ public final strictfp class DataStoreMock extends DataStore {
public void simulateWarning(String message) {
listeners.warning(message);
}
+
+ /**
+ * Returns a new dummy child having this data store as a parent.
+ *
+ * @return a dummy child.
+ */
+ public Resource newChild() {
+ return new AbstractResource(listeners, false) {
+ };
+ }
}
diff --git
a/storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java
b/storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java
index f4af5537be..f353231094 100644
---
a/storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java
+++
b/storage/sis-storage/src/test/java/org/apache/sis/storage/event/StoreListenersTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.storage.event;
import java.util.logging.Level;
import java.util.logging.LogRecord;
+import org.apache.sis.storage.Resource;
import org.apache.sis.storage.DataStoreMock;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.TestCase;
@@ -30,7 +31,7 @@ import static org.junit.Assert.*;
* Tests the {@link StoreListeners} class.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.0
* @module
*/
@@ -128,4 +129,36 @@ public final strictfp class StoreListenersTest extends
TestCase implements Store
assertEquals("simulateWarning", warning.getSourceMethodName());
assertEquals("The message", warning.getMessage());
}
+
+ /**
+ * Tests {@link StoreListeners#close()}. This event is handled in a
special way:
+ * close event on the parent resource causes the same event to be fired
for all
+ * children.
+ */
+ @Test
+ public void testClose() {
+ final Resource resource = store.newChild();
+ class Listener implements StoreListener<CloseEvent> {
+ /** Whether the resource has been closed. */boolean isClosed;
+
+ @Override public void eventOccured(CloseEvent event) {
+ assertSame(resource, event.getSource());
+ isClosed = true;
+ }
+ }
+ final Listener listener = new Listener();
+ /*
+ * First, register and unregister. No event should be received.
+ */
+ resource.addListener(CloseEvent.class, listener);
+ resource.removeListener(CloseEvent.class, listener);
+ store.close();
+ assertFalse(listener.isClosed);
+ /*
+ * Register and close. Now the event should be received.
+ */
+ resource.addListener(CloseEvent.class, listener);
+ store.close();
+ assertTrue(listener.isClosed);
+ }
}
diff --git
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
index 451059e7b3..2e353f687e 100644
---
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
+++
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java
@@ -55,7 +55,7 @@ import org.opengis.feature.FeatureType;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.3
* @since 0.8
* @module
*/
@@ -102,7 +102,7 @@ public final class Store extends StaxDataStore implements
FeatureSet {
} catch (FactoryException e) {
throw new DataStoreException(e);
}
- listeners.useWarningEventsOnly();
+ listeners.useReadOnlyEvents();
}
/**
@@ -284,6 +284,7 @@ public final class Store extends StaxDataStore implements
FeatureSet {
*/
@Override
public synchronized void close() throws DataStoreException {
+ listeners.close(); // Should never fail.
final Reader r = reader;
reader = null;
if (r != null) try {
diff --git
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/package-info.java
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/package-info.java
index 7bd8f4e1f4..0ef4f26ff1 100644
---
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/package-info.java
+++
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/package-info.java
@@ -56,7 +56,7 @@
* </ul>
*
* @author Johann Sorel (Geomatys)
- * @version 0.8
+ * @version 1.3
*
* @see <a href="https://en.wikipedia.org/wiki/GPS_Exchange_Format">GPS
Exchange Format on Wikipedia</a>
* @see <a href="http://www.topografix.com/GPX/1/1/">GPX 1.1 Schema
Documentation</a>
diff --git
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
index 22f7a27b1c..ce4fe09e2f 100644
---
a/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
+++
b/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/xml/stream/StaxDataStore.java
@@ -587,6 +587,10 @@ public abstract class StaxDataStore extends URIDataStore {
* Closes the input or output stream and releases any resources used by
this XML data store.
* This data store can not be used anymore after this method has been
invoked.
*
+ * <h4>Note for implementers</h4>
+ * Implementations should invoke {@code listeners.close()} on their first
line
+ * before to clear their resources and to invoke {@code super.close()}.
+ *
* @throws DataStoreException if an error occurred while closing the input
or output stream.
*/
@Override