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
commit 5b53df22a7eb2eb00be4849c3b311ff1bae1d6f7 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Apr 14 16:06:40 2022 +0200 `MetadataSource.lookup(…)` should verify if the metadata record exists (otherwise the `catch (MetadataStoreException)` blocks are ineffective). --- .../org/apache/sis/metadata/sql/Dispatcher.java | 6 +- .../apache/sis/metadata/sql/MetadataSource.java | 114 ++++++++++++++------- .../apache/sis/metadata/sql/TableHierarchy.java | 4 + .../apache/sis/storage/landsat/MetadataReader.java | 4 +- .../apache/sis/storage/geotiff/GeoTiffStore.java | 2 +- .../apache/sis/storage/netcdf/MetadataReader.java | 2 +- .../internal/storage/DocumentedStoreProvider.java | 45 ++++---- .../sis/internal/storage/MetadataBuilder.java | 22 ++-- .../apache/sis/internal/storage/ascii/Store.java | 2 +- .../org/apache/sis/internal/storage/csv/Store.java | 2 +- .../apache/sis/internal/storage/image/Store.java | 2 +- 11 files changed, 124 insertions(+), 81 deletions(-) diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java index 9eb83a9151..63a15907e4 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/Dispatcher.java @@ -56,7 +56,7 @@ import org.apache.sis.internal.util.Numerics; * * @author Touraïvane (IRD) * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.0 + * @version 1.2 * @since 0.8 * @module */ @@ -83,7 +83,7 @@ final class Dispatcher implements InvocationHandler { * have different {@code preferredIndex} values even if their {@link CachedStatement#type} value is the same, * since their {@link #identifier} values are different.</div> */ - byte preferredIndex; + int preferredIndex; /** * The metadata instance where to store the property (column) values, or {@code null} if not yet created. @@ -266,7 +266,7 @@ final class Dispatcher implements InvocationHandler { hasValue |= (fetchValue(info, impl.getMethod(dep)) != null); } if (hasValue) { - cache = this.cache; // Created by recursive 'invoke(…)' call above. + cache = this.cache; // Created by recursive `invoke(…)` call above. if (cache != null) { synchronized (cache) { value = method.invoke(cache); // Attempt a new computation. diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java index 269d2c570e..4cf0c3509c 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/MetadataSource.java @@ -110,7 +110,7 @@ import org.apache.sis.util.iso.Types; * * @author Touraïvane (IRD) * @author Martin Desruisseaux (IRD, Geomatys) - * @version 1.1 + * @version 1.2 * @since 0.8 * @module */ @@ -193,7 +193,7 @@ public class MetadataSource implements AutoCloseable { * } * } * - * @see #take(Class, int) + * @see #prepareStatement(Class, String, int) * @see #recycle(CachedStatement, int) */ private final CachedStatement[] statements; @@ -332,7 +332,7 @@ public class MetadataSource implements AutoCloseable { } /* * If the error is transient or has a transient cause, we will not save MetadataFallback.INSTANCE - * in the 'instance' field. The intent is to try again next time this method will be invoked, in + * in the `instance` field. The intent is to try again next time this method will be invoked, in * case the transient error has disappeared. */ for (Throwable cause = e; cause != null; cause = cause.getCause()) { @@ -508,13 +508,17 @@ public class MetadataSource implements AutoCloseable { } /** - * Returns a statement that can be reused for the given interface, or {@code null} if none. + * Returns a statement that can be reused for performing queries on the table for the specified interface. + * Callers must invoke this method in a block synchronized on {@code this}. * * @param type the interface for which to reuse a prepared statement. + * @param tableName value of {@code getTableName(type)}, or {@code null} for computing by this method. * @param preferredIndex index in the cache array where to search first. This is only a hint for increasing * the chances to find quickly a {@code CachedStatement} instance for the right type and identifier. */ - private CachedStatement take(final Class<?> type, final int preferredIndex) { + private CachedStatement prepareStatement(final Class<?> type, String tableName, final int preferredIndex) + throws SQLException + { assert Thread.holdsLock(this); if (preferredIndex >= 0 && preferredIndex < statements.length) { final CachedStatement statement = statements[preferredIndex]; @@ -530,7 +534,14 @@ public class MetadataSource implements AutoCloseable { return statement; } } - return null; + if (tableName == null) { + tableName = getTableName(type); + } + final SQLBuilder helper = helper(); + final String query = helper.clear().append("SELECT * FROM ") + .appendIdentifier(schema, tableName).append(" WHERE ") + .appendIdentifier(ID_COLUMN).append("=?").toString(); + return new CachedStatement(type, connection().prepareStatement(query), logFilter); } /** @@ -548,7 +559,7 @@ public class MetadataSource implements AutoCloseable { while (statements[preferredIndex] != null) { if (++preferredIndex >= statements.length) { /* - * If we reach this point, this means that the 'statements' pool has reached its maximal capacity. + * If we reach this point, this means that the `statements` pool has reached its maximal capacity. * Loop again on all statements in order to find the oldest one. We will close that old statement * and cache the given one instead. */ @@ -837,26 +848,64 @@ public class MetadataSource implements AutoCloseable { * @param identifier the identifier of the record for the metadata entity to be created. * This is usually the primary key of the record to search for. * @return an implementation of the required interface, or the code list element. - * @throws MetadataStoreException if a SQL query failed. + * @throws MetadataStoreException if a SQL query failed or if the metadata has not been found. */ public <T> T lookup(final Class<T> type, final String identifier) throws MetadataStoreException { ArgumentChecks.ensureNonNull("type", type); ArgumentChecks.ensureNonEmpty("identifier", identifier); + return type.cast(lookup(type, identifier, true)); + } + + /** + * Implementation of public {@link #lookup(Class, String)} method. + * + * <h4>Deferred database access</h4> + * This method may or may not query the database immediately, at implementation choice. + * It the database is not queried immediately, invalid identifiers may not be detected + * during this method invocation. Instead, an invalid identifier may be detected only + * when a getter method is invoked on the returned metadata object. In such case, + * an {@link org.apache.sis.util.collection.BackingStoreException} will be thrown + * at getter method invocation time. + * + * @param type the interface to implement or the {@link ControlledVocabulary} type. + * @param identifier the identifier of the record for the metadata entity to be created. + * @param verify whether to check for record existence. + * @return an implementation of the required interface, or the code list element. + * @throws MetadataStoreException if a SQL query failed or if the metadata has not been found. + */ + private Object lookup(final Class<?> type, final String identifier, boolean verify) throws MetadataStoreException { Object value; if (ControlledVocabulary.class.isAssignableFrom(type)) { value = getCodeList(type, identifier); } else { final CacheKey key = new CacheKey(type, identifier); /* - * IMPLEMENTATION NOTE: be careful to not invoke any method that may synchronize on 'this' - * inside the block synchronized on 'pool'. + * IMPLEMENTATION NOTE: be careful to not invoke any method that may synchronize on `this` + * inside a block synchronized on `pool` (implicit synchronization of `pool` method calls). */ - synchronized (pool) { - value = pool.get(key); - if (value == null && type.isInterface()) { - value = Proxy.newProxyInstance(classloader, - new Class<?>[] {type, MetadataProxy.class}, new Dispatcher(identifier, this)); - pool.put(key, value); + value = pool.get(key); + if (value == null && type.isInterface()) { + final Dispatcher toSearch = new Dispatcher(identifier, this); + value = Proxy.newProxyInstance(classloader, new Class<?>[] {type, MetadataProxy.class}, toSearch); + if (verify) try { + /* + * If the caller asked to verify whether the record exists, perform a query now. + * We trivially request the identifier, so the `getValue(…)` result should be + * the identifier itself (this is not verified). If the record does not exist, + * a `MetadataStoreException` is thrown by `getValue(…)`. + */ + synchronized (this) { + final Class<?> subType = TableHierarchy.subType(type, identifier); + CachedStatement result = prepareStatement(subType, null, toSearch.preferredIndex); + result.getValue(identifier, ID_COLUMN); // Check record existence. + toSearch.preferredIndex = recycle(result, toSearch.preferredIndex); + } + } catch (SQLException e) { + throw new MetadataStoreException(Errors.format(Errors.Keys.DatabaseError_2, type, identifier), e); + } + final Object replacement = pool.putIfAbsent(key, value); + if (replacement != null) { + value = replacement; } } /* @@ -908,12 +957,14 @@ public class MetadataSource implements AutoCloseable { /** * Invoked by {@link MetadataProxy} for fetching an attribute value from a table. + * It the database table does not contains a column for the property, this method returns {@code null}. + * A {@code null} value may also mean that the column exists but contains an SQL {@code NULL} value. * * @param info the interface type (together with cached information). * This is mapped to the table name in the database. * @param method the method invoked. This is mapped to the column name in the database. * @param toSearch contains the identifier and preferred index of the record to search. - * @return the value of the requested attribute. + * @return the value of the requested attribute, or {@code null} if none. * @throws SQLException if the SQL query failed. * @throws MetadataStoreException if a value was not found or can not be converted to the expected type. */ @@ -922,7 +973,7 @@ public class MetadataSource implements AutoCloseable { { /* * If the identifier is prefixed with a table name as in "{Organisation}identifier", - * the name between bracket is a subtype of the given 'type' argument. + * the name between bracket is a subtype of the given `type` argument. */ final Class<?> type = TableHierarchy.subType(info.getMetadataType(), toSearch.identifier); final Class<?> returnType = method.getReturnType(); @@ -940,17 +991,10 @@ public class MetadataSource implements AutoCloseable { } else { /* * Prepares the statement and executes the SQL query in this synchronized block. - * Note that the usage of 'result' must stay inside this synchronized block + * Note that the usage of `result` must stay inside this synchronized block * because we can not assume that JDBC connections are thread-safe. */ - CachedStatement result = take(type, Byte.toUnsignedInt(toSearch.preferredIndex)); - if (result == null) { - final SQLBuilder helper = helper(); - final String query = helper.clear().append("SELECT * FROM ") - .appendIdentifier(schema, tableName).append(" WHERE ") - .appendIdentifier(ID_COLUMN).append("=?").toString(); - result = new CachedStatement(type, connection().prepareStatement(query), logFilter); - } + CachedStatement result = prepareStatement(type, tableName, toSearch.preferredIndex); value = result.getValue(toSearch.identifier, columnName); isArray = (value instanceof java.sql.Array); if (isArray) { @@ -958,7 +1002,7 @@ public class MetadataSource implements AutoCloseable { value = array.getArray(); array.free(); } - toSearch.preferredIndex = (byte) recycle(result, Byte.toUnsignedInt(toSearch.preferredIndex)); + toSearch.preferredIndex = recycle(result, toSearch.preferredIndex); } } /* @@ -971,7 +1015,7 @@ public class MetadataSource implements AutoCloseable { Object element = Array.get(value, i); if (element != null) { if (isMetadata) { - element = lookup(elementType, element.toString()); + element = lookup(elementType, element.toString(), false); } else try { element = info.convert(elementType, element); } catch (UnconvertibleObjectException e) { @@ -987,13 +1031,13 @@ public class MetadataSource implements AutoCloseable { } } /* - * Now converts the value to its final type. To be strict, we should convert null values into empty collections - * if the return type is a collection type. But we leave this task to the caller (which is the Dispatcher class) - * for making easier to detect when a value is absent, for allowing Dispatcher to manage its cache. + * Now convert the value to its final type. To be strict, we should convert null values to empty collections + * if the return type is a collection type. But we leave this task to the caller (which is the `Dispatcher`) + * for making easier to detect when a value is absent, for allowing `Dispatcher` to manage its cache. */ if (value != null) { if (isMetadata) { - value = lookup(elementType, value.toString()); + value = lookup(elementType, value.toString(), false); } else try { value = info.convert(elementType, value); } catch (UnconvertibleObjectException e) { @@ -1045,7 +1089,7 @@ public class MetadataSource implements AutoCloseable { enumeration = EnumSet.noneOf((Class) elementType); } else { /* - * If 'returnType' is Collection.class, do not copy into a Set since a List + * If `returnType` is Collection.class, do not copy into a Set since a List * is probably good enough. Copy only if a Set is explicitly requested. */ if (Set.class.isAssignableFrom(returnType)) { @@ -1185,7 +1229,7 @@ public class MetadataSource implements AutoCloseable { isCloseScheduled = true; } else { // No more prepared statements. - final Connection c = this.connection; + final Connection c = connection; connection = null; helper = null; closeQuietly(c); diff --git a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java index bd8c757bf9..d362793ca4 100644 --- a/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java +++ b/core/sis-metadata/src/main/java/org/apache/sis/metadata/sql/TableHierarchy.java @@ -112,6 +112,10 @@ final class TableHierarchy { * For example if the given type is {@code Party.class} and the given identifier is * {@code "{Organisation}EPSG"}, then this method returns {@code Organisation.class}. * Otherwise this method returns {@code type} unchanged. + * + * @param type base metadata type. + * @param identifier primary key in the database. + * @return actual type of the metadata object. */ static Class<?> subType(Class<?> type, final String identifier) { if (identifier.charAt(0) == TYPE_OPEN) { diff --git a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java index 4b0f2be036..411542f275 100644 --- a/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java +++ b/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/landsat/MetadataReader.java @@ -476,8 +476,8 @@ final class MetadataReader extends MetadataBuilder { case "OUTPUT_FORMAT": { String name = value; if (Constants.GEOTIFF.equalsIgnoreCase(name)) try { - name = Constants.GEOTIFF; // Because 'metadata.setFormat(…)' is case-sensitive. - setFormat(name); + name = Constants.GEOTIFF; // Because `metadata.setPredefinedFormat(…)` is case-sensitive. + setPredefinedFormat(name); break; } catch (MetadataStoreException e) { warning(key, null, e); 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 783f177233..d0a2c77d26 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 @@ -285,7 +285,7 @@ public class GeoTiffStore extends DataStore implements Aggregate { */ final void setFormatInfo(final MetadataBuilder builder) { try { - builder.setFormat(Constants.GEOTIFF); + builder.setPredefinedFormat(Constants.GEOTIFF); } catch (MetadataStoreException e) { builder.addFormatName(Constants.GEOTIFF); listeners.warning(e); diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java index 92fe35edd9..15c099b14b 100644 --- a/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java +++ b/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java @@ -666,7 +666,7 @@ split: while ((start = CharSequences.skipLeadingWhitespaces(value, start, lengt final String[] format = decoder.getFormatDescription(); String id = format[0]; if (NetcdfStoreProvider.NAME.equalsIgnoreCase(id)) try { - setFormat(NetcdfStoreProvider.NAME); + setPredefinedFormat(NetcdfStoreProvider.NAME); id = null; } catch (MetadataStoreException e) { // Will add `id` at the end of this method. diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java index 3e27da39ea..0ea7a4eb80 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/DocumentedStoreProvider.java @@ -31,7 +31,7 @@ import org.apache.sis.internal.system.Modules; * The primary key in the {@code MD_Format} table must be the name given at construction time. * * @author Martin Desruisseaux (Geomatys) - * @version 0.8 + * @version 1.2 * @since 0.8 * @module */ @@ -43,11 +43,11 @@ public abstract class DocumentedStoreProvider extends URIDataStore.Provider { private final String name; /** - * {@code true} if the call to {@link #getFormat()} caught an exception. In such case, - * we log a warning only the first time and use a finer logging level the other times. - * The intent is to avoid polluting the logs with too many warnings. + * The format, created when first requested. + * + * @see #getFormat(StoreListeners) */ - private volatile boolean logged; + private transient Format format; /** * Creates a new provider. @@ -88,29 +88,24 @@ public abstract class DocumentedStoreProvider extends URIDataStore.Provider { * @param listeners where to report the warning in case of error, or {@code null} if none. * @return a description of the data format. */ - public final Format getFormat(final StoreListeners listeners) { - /* - * Note: this method does not cache the format because such caching is already done by MetadataSource. - */ - if (name != null) try { - return MetadataSource.getProvided().lookup(Format.class, name); - } catch (MetadataStoreException e) { - if (listeners != null) { - listeners.warning(e); - } else { - final Level level; - if (!logged) { - logged = true; // Not atomic - not a big deal if we use warning level twice. - level = Level.WARNING; - } else { - level = Level.FINE; - } - final LogRecord record = Resources.forLocale(null).getLogRecord(level, + public final synchronized Format getFormat(final StoreListeners listeners) { + if (format == null) { + if (name != null) try { + return format = MetadataSource.getProvided().lookup(Format.class, name); + } catch (MetadataStoreException e) { + final LogRecord record = Resources.forLocale(null).getLogRecord(Level.WARNING, Resources.Keys.CanNotGetCommonMetadata_2, getShortName(), e.getLocalizedMessage()); + record.setSourceClassName(getClass().getCanonicalName()); + record.setSourceMethodName("getFormat"); record.setLoggerName(Modules.STORAGE); - Logging.log(getClass(), "getFormat", record); + if (listeners != null) { + listeners.warning(record); + } else { + Logging.getLogger(Modules.STORAGE).log(record); + } } + format = super.getFormat(); } - return super.getFormat(); + return format; } } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java index 2e83572902..dac07899b8 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java @@ -1008,7 +1008,7 @@ public class MetadataBuilder { * * {@preformat java * try { - * metadata.setFormat("MyFormat"); + * metadata.setPredefinedFormat("MyFormat"); * } catch (MetadataStoreException e) { * metadata.addFormatName("MyFormat"); * listeners.warning(null, e); @@ -1023,7 +1023,7 @@ public class MetadataBuilder { * @see #addCompression(CharSequence) * @see #addFormatName(CharSequence) */ - public final void setFormat(final String abbreviation) throws MetadataStoreException { + public final void setPredefinedFormat(final String abbreviation) throws MetadataStoreException { if (abbreviation != null && abbreviation.length() != 0) { if (format == null) { format = MetadataSource.getProvided().lookup(Format.class, abbreviation); @@ -3089,12 +3089,12 @@ parse: for (int i = 0; i < length;) { * <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/alternateTitle}</li> * </ul> * - * If this method is used together with {@link #setFormat(String)}, - * then {@code setFormat} should be invoked <strong>before</strong> this method. + * If this method is used together with {@link #setPredefinedFormat(String)}, + * then {@code setPredefinedFormat(…)} should be invoked <strong>before</strong> this method. * * @param value the format name, or {@code null} for no-operation. * - * @see #setFormat(String) + * @see #setPredefinedFormat(String) * @see #setFormatEdition(CharSequence) * @see #addCompression(CharSequence) */ @@ -3119,12 +3119,12 @@ parse: for (int i = 0; i < length;) { * <li>{@code metadata/identificationInfo/resourceFormat/formatSpecificationCitation/edition}</li> * </ul> * - * If this method is used together with {@link #setFormat(String)}, - * then {@code setFormat} should be invoked <strong>before</strong> this method. + * If this method is used together with {@link #setPredefinedFormat(String)}, + * then {@code setPredefinedFormat(…)} should be invoked <strong>before</strong> this method. * * @param value the format edition, or {@code null} for no-operation. * - * @see #setFormat(String) + * @see #setPredefinedFormat(String) * @see #addFormatName(CharSequence) */ public final void setFormatEdition(final CharSequence value) { @@ -3148,12 +3148,12 @@ parse: for (int i = 0; i < length;) { * <li>{@code metadata/identificationInfo/resourceFormat/fileDecompressionTechnique}</li> * </ul> * - * If this method is used together with {@link #setFormat(String)}, - * then {@code setFormat} should be invoked <strong>before</strong> this method. + * If this method is used together with {@link #setPredefinedFormat(String)}, + * then {@code setPredefinedFormat(…)} should be invoked <strong>before</strong> this method. * * @param value the compression name, or {@code null} for no-operation. * - * @see #setFormat(String) + * @see #setPredefinedFormat(String) * @see #addFormatName(CharSequence) */ public final void addCompression(final CharSequence value) { diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java index 2409b120e0..ead3dceeca 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java @@ -332,7 +332,7 @@ cellsize: if (value != null) { readHeader(); final MetadataBuilder builder = new MetadataBuilder(); try { - builder.setFormat("ASCGRD"); + builder.setPredefinedFormat("ASCGRD"); } catch (MetadataStoreException e) { builder.addFormatName(StoreProvider.NAME); listeners.warning(e); 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 0398172c9e..7580d9ce41 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 @@ -640,7 +640,7 @@ final class Store extends URIDataStore implements FeatureSet { final MetadataBuilder builder = new MetadataBuilder(); final String format = (timeEncoding != null) && hasTrajectories ? StoreProvider.MOVING : StoreProvider.NAME; try { - builder.setFormat(format); + builder.setPredefinedFormat(format); } catch (MetadataStoreException e) { builder.addFormatName(format); listeners.warning(e); diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/Store.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/Store.java index 299b1db493..148c7f50d7 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/Store.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/image/Store.java @@ -344,7 +344,7 @@ loop: for (int convention=0;; convention++) { final MetadataBuilder builder = new MetadataBuilder(); final String format = reader().getFormatName(); try { - builder.setFormat(format); + builder.setPredefinedFormat(format); } catch (MetadataStoreException e) { builder.addFormatName(format); listeners.warning(Level.FINE, null, e);