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 fce18cb382 Reduce the verbosity of log record or error message during XML unmarshalling. - Some log records were repeated many times. - JAXBException with very long messages had the message repeated in their causes. fce18cb382 is described below commit fce18cb3826104e0094a1daf79463baabf442ccd Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sat Jan 27 17:49:58 2024 +0100 Reduce the verbosity of log record or error message during XML unmarshalling. - Some log records were repeated many times. - JAXBException with very long messages had the message repeated in their causes. --- .../main/org/apache/sis/xml/ReferenceResolver.java | 74 ++++++---- .../main/org/apache/sis/xml/bind/Context.java | 47 +++++-- .../apache/sis/xml/util/ExceptionSimplifier.java | 150 +++++++++++++++++++++ .../apache/sis/xml/util/ExternalLinkHandler.java | 8 +- .../main/org/apache/sis/io/wkt/Element.java | 3 +- .../apache/sis/referencing/internal/Resources.java | 5 + .../sis/referencing/internal/Resources.properties | 1 + .../referencing/internal/Resources_fr.properties | 1 + .../sis/referencing/util/ReferencingUtilities.java | 5 +- .../org/apache/sis/storage/base/PRJDataStore.java | 8 +- .../org/apache/sis/storage/base/URIDataStore.java | 19 ++- .../main/org/apache/sis/util/Exceptions.java | 13 +- .../main/org/apache/sis/util/resources/Errors.java | 15 ++- .../apache/sis/util/resources/Errors.properties | 3 +- .../apache/sis/util/resources/Errors_fr.properties | 3 +- .../resources/ResourceInternationalString.java | 4 +- 16 files changed, 292 insertions(+), 67 deletions(-) diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java index 8d5828e801..68ef4e7bfb 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/ReferenceResolver.java @@ -157,6 +157,7 @@ public class ReferenceResolver { * </ul> * * If an object is found but is not of the class declared in {@code type}, + * or if an {@link Exception} was thrown during object unmarshalling, * then this method emits a warning and returns {@code null}. * * @param <T> the compile-time type of the {@code type} argument. @@ -173,6 +174,7 @@ public class ReferenceResolver { return null; } final Object object; + final short reasonIfNull; // An Errors.Key value with one parameter, or 0. final Context c = (context instanceof Context) ? (Context) context : Context.current(); if (!href.isAbsolute() && Strings.isNullOrEmpty(href.getPath())) { /* @@ -187,10 +189,12 @@ public class ReferenceResolver { return null; } object = Context.getObjectForID(c, fragment); + reasonIfNull = Errors.Keys.NotABackwardReference_1; } else try { /* - * URI to an external document. We let `ExternalLinkHandler` decide how to replace relative URI - * by absolute URI. It may depend on whether user has specified a `javax.xml.stream.XMLResolver` + * URI to an external document. If a `javax.xml.stream.XMLResolver` property was set on the unmarshaller, + * use the user-supplied `URIResolver`. If there is no URI resolver or the URI resolver can not resolve, + * fallback on the Apache SIS `ExternalLinkHandler` implementation. The latter is the usual case. */ final ExternalLinkHandler handler = Context.linkHandler(c); Source source = null; @@ -200,32 +204,38 @@ public class ReferenceResolver { source = externalSourceResolver.resolve(href.toString(), base.toString()); } } - if (source == null) { - source = handler.openReader(href); + if (source == null && (source = handler.openReader(href)) == null) { + reasonIfNull = Errors.Keys.CanNotResolveAsAbsolutePath_1; + object = null; + } else { + object = resolveExternal(context, source); + reasonIfNull = 0; } - object = (source != null) ? resolveExternal(context, source) : null; } catch (Exception e) { ExternalLinkHandler.warningOccured(href, e); return null; } /* - * At this point, the referenced object has been fetched. - * Verify its validity. + * At this point, the referenced object has been fetched or unmarshalled. + * The result may be null, in which case the warning to emit depends on the + * reason why the object is null: could not resolve, or could not unmarshall. */ if (type.isInstance(object)) { return type.cast(object); - } else { - final short key; - final Object[] args; - if (object == null) { - key = Errors.Keys.NotABackwardReference_1; - args = new Object[] {href.toString()}; - } else { - key = Errors.Keys.UnexpectedTypeForReference_3; - args = new Object[] {href.toString(), type, object.getClass()}; + } + final short key; + final Object[] args; + if (object == null) { + if (reasonIfNull == 0) { + return null; } - Context.warningOccured(c, ReferenceResolver.class, "resolve", Errors.class, key, args); + key = reasonIfNull; + args = new Object[] {href.toString()}; + } else { + key = Errors.Keys.UnexpectedTypeForReference_3; + args = new Object[] {href.toString(), type, object.getClass()}; } + Context.warningOccured(c, ReferenceResolver.class, "resolve", Errors.class, key, args); return null; } @@ -245,9 +255,18 @@ public class ReferenceResolver { * </ul> * The resolved URL, if known, should be available in {@link Source#getSystemId()}. * + * <h4>Error handling</h4> + * The default implementation keeps a cache during the execution of an {@code XML.unmarshall(…)} method + * (or actually, during a {@linkplain MarshallerPool pooled unmarshaller} method). + * If an exception is thrown during the document unmarshalling, this failure is also recorded in the cache. + * Therefor, the exception is thrown only during the first attempt to read the document + * and {@code null} is returned directly on next attempts for the same source. + * Exceptions thrown by this method are caught by {@link #resolve(MarshalContext, Class, XLink) resolve(…)} + * and reported as warnings. + * * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. * @param source source of the document specified by the {@code xlink:href} attribute value. - * @return an object for the given source, or {@code null} if none. + * @return an object for the given source, or {@code null} if none, for example because of failure in a previous attempt. * @throws Exception if an error occurred while opening or parsing the document. * * @since 1.5 @@ -278,12 +297,12 @@ public class ReferenceResolver { * and the URI fragment to use as a GML identifier. Check if the document is in the cache. * Note that if the fragment is null, then by convention we lookup for the whole document. */ - final Context c = Context.current(); + final Context c = (context instanceof Context) ? (Context) context : Context.current(); if (c != null) { final Object object = c.getExternalObjectForID(document, fragment); if (object != null) { XmlUtilities.close(source); - return object; + return (object != Context.INVALID_OBJECT) ? object : null; } } /* @@ -297,10 +316,17 @@ public class ReferenceResolver { ((Pooled) m).forIncludedDocument(document); } final Object object; - if (uri != null) { - object = m.unmarshal(uri.toURL()); - } else { - object = m.unmarshal(source); + try { + if (uri != null) { + object = m.unmarshal(uri.toURL()); + } else { + object = m.unmarshal(source); + } + } catch (Exception e) { + if (c != null) { + c.cacheDocument(document, Context.INVALID_OBJECT); + } + throw e; } pool.recycle(m); /* diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java index 6558346f42..e66c25496d 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/Context.java @@ -117,8 +117,19 @@ public final class Context extends MarshalContext { */ private static final ThreadLocal<Context> CURRENT = new ThreadLocal<>(); + /** + * A sentinel value meaning that unmarshalling of a document was already attempted before and failed. + * This is used for documents referenced from a larger document using {@code xlink:href}. + * + * @see #getExternalObjectForID(Object, String) + * @see #cacheDocument(Object, Object) + */ + public static final Object INVALID_OBJECT = Void.TYPE; + /** * The logger to use for warnings that are specific to XML. + * + * @see #warningOccured(Context, Level, Class, String, Throwable, Class, short, Object...) */ public static final Logger LOGGER = Logger.getLogger(Loggers.XML); @@ -180,6 +191,10 @@ public final class Context extends MarshalContext { * At marhalling time, this map is used for avoiding duplicated identifiers in the same XML document. * At unmarshalling time, this is used for getting a previously unmarshalled object from its identifier. * + * <p>By convention, the {@code null} key is associated to the whole document. This convention is used if + * the document being unmarshalled is part of a larger document and was referenced by {@code xlink:href}. + * In such case, this map is also a value of the {@link #documentToXmlids} map.</p> + * * @see #getObjectForID(Context, String) */ private final Map<String,Object> xmlidToObject; @@ -202,8 +217,10 @@ public final class Context extends MarshalContext { * from a file or an URL, {@code systemId} should be the value of {@link java.net.URI#toASCIIString()} for * consistency with {@link javax.xml.transform.stream.StreamSource}. However, URI instances are preferred * in this map because the {@link URI#equals(Object)} method applies some rules regarding case-sensitivity - * that {@link String#equals(Object)} cannot know. Values of the map are the {@link #xmlidToObject} maps of - * the corresponding document. By convention, the object associated to the null key is the whole document.</p> + * that {@link String#equals(Object)} cannot know.</p> + * + * <p>Values of this map are the {@link #xmlidToObject} maps of the corresponding document. + * See {@link #xmlidToObject} for a description of the meaning of those maps.</p> */ private final Map<Object, Map<String,Object>> documentToXmlids; @@ -219,6 +236,8 @@ public final class Context extends MarshalContext { /** * The object to inform about warnings, or {@code null} if none. + * + * @see org.apache.sis.xml.XML#WARNING_FILTER */ private final Filter logFilter; @@ -679,12 +698,20 @@ public final class Context extends MarshalContext { * By convention, a null {@code id} returns the whole document.</p> * * @param systemId document identifier (without the fragment part) as an {@link URI} or a {@link String}. - * @param id the fragment part of the URI identifying the object to get. - * @return the object associated to the given identifier, or {@code null} if none. + * @param fragment the fragment part of the URI, or {@code null} for the whole document. + * @return the object associated to the given identifier, or {@code null} if none, + * or {@link #INVALID_OBJECT} if a parsing was previously attempted and failed. */ - public final Object getExternalObjectForID(final Object systemId, final String id) { + public final Object getExternalObjectForID(final Object systemId, final String fragment) { final Map<String,Object> cache = documentToXmlids.get(systemId); - return (cache != null) ? cache.get(id) : null; + if (cache == null) { + return null; + } + final Object value = cache.get(fragment); + if (value == null && cache.get(null) == INVALID_OBJECT) { + return INVALID_OBJECT; + } + return value; } /** @@ -693,7 +720,7 @@ public final class Context extends MarshalContext { * The fragment part is obtained by {@link #getExternalObjectForID(Object, String)}. * * @param systemId document identifier (without the fragment part) as an {@link URI} or a {@link String}. - * @param document the document to cache. + * @param document the document to cache, or {@link #INVALID_OBJECT} for recording a failure to read the document. */ public final void cacheDocument(final Object systemId, final Object document) { final Map<String, Object> cache = documentToXmlids.get(systemId); @@ -766,7 +793,7 @@ public final class Context extends MarshalContext { /** * Sends a warning to the warning listener if there is one, or logs the warning otherwise. - * In the latter case, this method logs to the given logger. + * In the latter case, this method logs to {@link #LOGGER}. * * <p>If the given {@code resources} is {@code null}, then this method will build the log * message from the {@code exception}.</p> @@ -776,7 +803,7 @@ public final class Context extends MarshalContext { * @param classe the class to declare as the warning source. * @param method the name of the method to declare as the warning source. * @param exception the exception thrown, or {@code null} if none. - * @param resources either {@code Errors.class}, {@code Messages.class} or {@code null} for the exception message. + * @param resources either {@code Errors.class} or {@code Messages.class}, or {@code null} for the exception message. * @param key the resource keys as one of the constants defined in the {@code Keys} inner class. * @param arguments the arguments to be given to {@code MessageFormat} for formatting the log message. */ @@ -837,7 +864,7 @@ public final class Context extends MarshalContext { /** * Convenience method for sending a warning for the given exception. - * The logger will be {@code "org.apache.sis.xml"}. + * The log message will be the message of the given exception. * * @param context the current context, or {@code null} if none. * @param classe the class to declare as the warning source. diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExceptionSimplifier.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExceptionSimplifier.java new file mode 100644 index 0000000000..a7cac1b5b1 --- /dev/null +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExceptionSimplifier.java @@ -0,0 +1,150 @@ +/* + * 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.xml.util; + +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import org.xml.sax.SAXParseException; +import org.apache.sis.system.Loggers; +import org.apache.sis.xml.bind.Context; +import org.apache.sis.util.resources.Errors; + + +/** + * Simplifies an exception before to log it. This class reduces log flooding when an exception has a very long message + * (e.g. JAXB enumerating all elements that it was expecting) and that long message is repeated in the exception cause. + * This class also handles the identification of the location where the error occurred. + * + * @author Martin Desruisseaux (Geomatys) + */ +public final class ExceptionSimplifier { + /** + * The key to use for producing an error message, or 0 for using the exception message. + * Shall be a constant from {@link Errors.Keys}. + */ + private short errorKey; + + /** + * The values to insert in the error message, or {@code null} for using the exception message. + */ + private Object[] errorValues; + + /** + * The exception. + */ + public final Exception exception; + + /** + * Simplifies the given exception if possible, and produces an error message. + * + * @param source the source (URI or Path) that couldn't be parsed, or {@code null} if unknown. + * @param exception the error that occurred while parsing the source. + */ + public ExceptionSimplifier(Object source, Exception exception) { + int line = -1, column = -1; + for (Throwable cause = exception; cause != null; cause = cause.getCause()) { + if (cause instanceof SAXParseException) { + var s = (SAXParseException) cause; + if ((line | column) < 0) { + line = s.getLineNumber(); + column = s.getColumnNumber(); + } + if (source == null) { + source = s.getPublicId(); + if (source == null) { + source = s.getSystemId(); + } + } + } + if (cause != exception) { + final String msg = exception.getMessage(); + if (msg != null) { + final String s = cause.getMessage(); + if (s != null && !msg.contains(s)) break; + } + if (exception.getClass().isInstance(cause)) { + exception = (Exception) cause; + } + } + } + this.exception = exception; + if (source == null) { + final ExternalLinkHandler handler = Context.linkHandler(Context.current()); + if (handler != null) { + source = handler.getBase(); + } + } + if (source != null) { + if ((line | column) < 0) { + errorKey = Errors.Keys.CanNotRead_1; + errorValues = new Object[] {source}; + } else { + errorKey = Errors.Keys.CanNotRead_3; + errorValues = new Object[] {source, line, column}; + } + } + } + + /** + * Returns the error message. + * + * @param locale desired locale for the error message. + * @return the error message, or {@code null} if none. + */ + public String getMessage(final Locale locale) { + if (errorKey != 0) { + return Errors.getResources(locale).getString(errorKey, errorValues); + } else { + return exception.getMessage(); + } + } + + /** + * Sends the exception to the warning listener if there is one, or logs the warning otherwise. + * In the latter case, this method logs to {@link Context#LOGGER}. + * + * @param context the current context, or {@code null} if none. + * @param classe the class to declare as the warning source. + * @param method the name of the method to declare as the warning source. + */ + public void report(final Context context, final Class<?> classe, final String method) { + Context.warningOccured(context, Level.WARNING, classe, method, exception, + (errorKey != 0) ? Errors.class : null, errorKey, errorValues); + } + + /** + * Creates a log record for the warning. + * + * @param classe the class to declare as the warning source. + * @param method the name of the method to declare as the warning source. + * @return the log record. + */ + public LogRecord record(final Class<?> classe, final String method) { + final LogRecord record; + if (errorKey != 0) { + record = Errors.getResources((Locale) null).getLogRecord(Level.WARNING, errorKey, errorValues); + } else { + record = new LogRecord(Level.WARNING, exception.getMessage()); + } + record.setLoggerName(Loggers.XML); + record.setSourceClassName(classe.getCanonicalName()); + record.setSourceMethodName(method); + record.setThrown(exception); + return record; + } +} diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java index 9dbbdc9a22..06fdeddf4d 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java @@ -21,7 +21,6 @@ import java.io.InputStream; import java.net.URL; import java.net.URI; import java.net.URISyntaxException; -import java.util.logging.Level; import javax.xml.stream.Location; import javax.xml.stream.XMLResolver; import javax.xml.stream.XMLInputFactory; @@ -181,12 +180,11 @@ public class ExternalLinkHandler { * The latter assumption is valid if {@code ReferenceResolver.resolve(…)} is the only * code invoking, directly or indirectly, this {@code warning(…)} method. * - * @param href the URI that cannot be parsed. - * @param cause the exception that occurred while trying to process the document. + * @param href the URI that cannot be parsed. + * @param cause the exception that occurred while trying to process the document. */ public static void warningOccured(final Object href, final Exception cause) { - Context.warningOccured(Context.current(), Level.WARNING, ReferenceResolver.class, "resolve", - cause, Errors.class, Errors.Keys.CanNotRead_1, href); + new ExceptionSimplifier(href, cause).report(Context.current(), ReferenceResolver.class, "resolve"); } /** diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Element.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Element.java index 8b0f5bd021..425f7808bb 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Element.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/Element.java @@ -31,6 +31,7 @@ import org.apache.sis.util.Exceptions; import org.apache.sis.util.CharSequences; import org.apache.sis.util.resources.Errors; import org.apache.sis.referencing.util.WKTKeywords; +import org.apache.sis.referencing.internal.Resources; import org.apache.sis.util.internal.CollectionsExt; import static org.apache.sis.util.CharSequences.skipLeadingWhitespaces; @@ -404,7 +405,7 @@ final class Element { * @return the exception to be thrown. */ final ParseException parseFailed(final Exception cause) { - return new UnparsableObjectException(errorLocale, Errors.Keys.ErrorIn_2, + return new UnparsableObjectException(errorLocale, Resources.Keys.CannotParseElement_2, new String[] {keyword, Exceptions.getLocalizedMessage(cause, errorLocale)}, offset).initCause(cause); } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java index df0be0c83f..ac36d0818d 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.java @@ -167,6 +167,11 @@ public class Resources extends IndexedResourceBundle { */ public static final short CanNotUseGeodeticParameters_2 = 9; + /** + * Cannot parse the “{0}” element: {1} + */ + public static final short CannotParseElement_2 = 101; + /** * Axis directions {0} and {1} are colinear. */ diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties index 18b3aba29d..1ceec59dae 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources.properties @@ -22,6 +22,7 @@ # Information messages or non-fatal warnings # AmbiguousEllipsoid_1 = Ambiguity between inverse flattening and semi minor axis length for \u201c{0}\u201d. Using inverse flattening. +CannotParseElement_2 = Cannot parse the \u201c{0}\u201d element: {1} ConformanceMeansDatumShift = This result indicates if a datum shift method has been applied. ConstantProjParameterValue_1 = This parameter is shown for completeness, but should never have a value different than {0} for this projection. DeprecatedCode_3 = Code \u201c{0}\u201d is deprecated and replaced by code {1}. Reason is: {2} diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties index 98e953dc70..27a9408e59 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Resources_fr.properties @@ -27,6 +27,7 @@ # Information messages or non-fatal warnings # AmbiguousEllipsoid_1 = Ambigu\u00eft\u00e9 entre l\u2019aplatissement et la longueur du semi-axe mineur pour \u00ab\u202f{0}\u202f\u00bb. Utilise l\u2019aplatissement. +CannotParseElement_2 = Ne peut pas d\u00e9coder l\u2019\u00e9l\u00e9ment \u00ab\u202f{0}\u202f\u00bb\u00a0: {1} ConformanceMeansDatumShift = Ce r\u00e9sultat indique si un changement de r\u00e9f\u00e9rentiel a \u00e9t\u00e9 appliqu\u00e9. ConstantProjParameterValue_1 = Ce param\u00e8tre est montr\u00e9 pour \u00eatre plus complet, mais sa valeur ne devrait jamais \u00eatre diff\u00e9rente de {0} pour cette projection. DeprecatedCode_3 = Le code \u00ab\u202f{0}\u202f\u00bb est d\u00e9pr\u00e9ci\u00e9 et remplac\u00e9 par le code {1}. La raison est\u00a0: {2} diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java index d0bb28fc88..4234653bd1 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/util/ReferencingUtilities.java @@ -19,6 +19,7 @@ package org.apache.sis.referencing.util; import java.util.Map; import java.util.HashMap; import java.util.Collection; +import java.util.NoSuchElementException; import javax.measure.Unit; import javax.measure.quantity.Angle; import org.opengis.annotation.UML; @@ -220,6 +221,7 @@ public final class ReferencingUtilities extends Static { * @param addTo where to add the single CRS in order to obtain a flat view of {@code source}. * @return {@code true} if this method found only single CRS in {@code source}, in which case {@code addTo} * got the same content (assuming that {@code addTo} was empty prior this method call). + * @throws NoSuchElementException if a CRS component is missing. * @throws ClassCastException if a CRS is neither a {@link SingleCRS} or a {@link CompoundCRS}. * * @see org.apache.sis.referencing.CRS#getSingleComponents(CoordinateReferenceSystem) @@ -243,10 +245,11 @@ public final class ReferencingUtilities extends Static { final String message; if (candidate instanceof NilObject) { message = Errors.format(Errors.Keys.NilObject_1, Identifiers.getNilReason((NilObject) candidate)); + throw new NoSuchElementException(message); } else { message = Errors.format(Errors.Keys.NestedElementNotAllowed_1, getInterface(candidate)); + throw new ClassCastException(message); } - throw new ClassCastException(message); } } return sameContent; diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java index dbfeba3ab8..7dcbe0f4c9 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/PRJDataStore.java @@ -47,6 +47,7 @@ import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Classes; import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.xml.util.ExceptionSimplifier; /** @@ -138,9 +139,12 @@ public abstract class PRJDataStore extends URIDataStore { listeners.warning(cannotReadAuxiliaryFile(extension)); return Optional.empty(); } - if (content.source != null) { + if (content.source != null) try { // ClassCastException handled by `catch` statement below. return Optional.of(type.cast(readXML(content.source))); + } catch (JAXBException e) { + var s = new ExceptionSimplifier(content.getFilename(), e); + throw new DataStoreException(s.getMessage(getLocale()), s.exception); } final String wkt = content.toString(); final StoreFormat format = new StoreFormat(dataLocale, timezone, null, listeners); @@ -165,7 +169,7 @@ public abstract class PRJDataStore extends URIDataStore { } catch (NoSuchFileException | FileNotFoundException e) { listeners.warning(cannotReadAuxiliaryFile(extension), e); return Optional.empty(); - } catch (IOException | ParseException | JAXBException | ClassCastException e) { + } catch (IOException | ParseException | ClassCastException e) { cause = e; } final var e = new DataStoreReferencingException(cannotReadAuxiliaryFile(extension), cause); diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java index 3427bb2b73..0862772199 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/URIDataStore.java @@ -51,6 +51,7 @@ import org.apache.sis.util.ArraysExt; import org.apache.sis.util.iso.Names; import org.apache.sis.xml.XML; import org.apache.sis.xml.util.URISource; +import org.apache.sis.xml.util.ExceptionSimplifier; /** @@ -328,31 +329,35 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R * @param builder where to merge the metadata. */ protected final void mergeAuxiliaryMetadata(final MetadataBuilder builder) { + Object spec = null; // Used only for formatting error message. Object metadata = null; - Exception error = null; try { final URI source; final InputStream input; final Path path = getMetadataPath(); if (path != null) { + spec = path; source = path.toUri(); input = open(path); } else { source = getMetadataURI(); if (source == null) return; + spec = source; input = source.toURL().openStream(); } metadata = readXML(input, source); } catch (URISyntaxException | IOException e) { - error = e; + listeners.warning(cannotReadAuxiliaryFile("xml"), e); } catch (JAXBException e) { - final Throwable cause = e.getCause(); - error = (cause instanceof IOException) ? (Exception) cause : e; + Throwable cause = e.getCause(); + if (cause instanceof IOException) { + listeners.warning(cannotReadAuxiliaryFile("xml"), (Exception) cause); + } else { + listeners.warning(new ExceptionSimplifier(spec, e).record(URIDataStore.class, "mergeAuxiliaryMetadata")); + } } if (metadata != null) { builder.mergeMetadata(metadata, getLocale()); - } else if (error != null) { - listeners.warning(cannotReadAuxiliaryFile("xml"), error); } } @@ -384,7 +389,7 @@ public abstract class URIDataStore extends DataStore implements StoreResource, R java.util.logging.Filter handler = (record) -> { record.setLoggerName(null); // For allowing `listeners` to use the provider's logger name. listeners.warning(record); - return true; + return false; }; // Cannot use Map.of(…) because it does not accept null values. Map<String,Object> properties = new HashMap<>(8); diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java index bfd796116e..f1d6514640 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Exceptions.java @@ -192,15 +192,11 @@ public final class Exceptions extends Static { * <li>It is an instance of {@link BackingStoreException} (typically wrapping a checked exception).</li> * <li>It is an instance of {@link UncheckedIOException} (wrapping a {@link java.io.IOException}).</li> * <li>It is an instance of {@link DirectoryIteratorException} (wrapping a {@link java.io.IOException}).</li> - * <li>It is a parent type of the cause. For example, some JDBC drivers wrap {@link SQLException} - * in other {@code SQLException} without additional information.</li> + * <li>It is a parent type of its cause. For example, some JDBC drivers wrap {@link SQLException} in another + * {@code SQLException} without additional information. When the wrapper is a parent class of the cause, + * details about the reason are less accessible.</li> * </ul> * - * <div class="note"><b>Note:</b> - * {@link java.security.PrivilegedActionException} is also a wrapper exception, but is not included in above list - * because it is used in very specific contexts. Furthermore, classes related to security manager are deprecated - * since Java 17.</div> - * * This method uses only the exception class as criterion; * it does not verify if the exception messages are the same. * @@ -233,6 +229,9 @@ public final class Exceptions extends Static { /** * Unwraps and copies suppressed exceptions from the given source to the given target. + * + * @param source the exception from which to copy suppressed exceptions. + * @param target the exception where to add suppressed exceptions. */ private static void copySuppressed(final Exception source, final Throwable target) { for (final Throwable suppressed : source.getSuppressed()) { diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java index dfe30d9602..0aba100be6 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java @@ -153,11 +153,21 @@ public class Errors extends IndexedResourceBundle { */ public static final short CanNotRead_1 = 12; + /** + * Cannot read “{0}” at line {1}, column {2}. + */ + public static final short CanNotRead_3 = 34; + /** * Cannot represent “{1}” in a strictly standard-compliant {0} format. */ public static final short CanNotRepresentInFormat_2 = 13; + /** + * Cannot resolve “{0}” as an absolute path. + */ + public static final short CanNotResolveAsAbsolutePath_1 = 205; + /** * Cannot set a value for parameter “{0}”. */ @@ -288,11 +298,6 @@ public class Errors extends IndexedResourceBundle { */ public static final short ErrorInFileAtLine_2 = 33; - /** - * Error in “{0}”: {1} - */ - public static final short ErrorIn_2 = 34; - /** * A size of {1} elements is excessive for the “{0}” list. */ diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties index 46fab02978..f134a437e3 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties @@ -42,7 +42,9 @@ CanNotParse_1 = Cannot parse \u201c{0}\u201d. CanNotProcessProperty_2 = Cannot process property \u201c{0}\u201d. The reason is: {1} CanNotProcessPropertyAtPath_3 = Cannot process property \u201c{1}\u201d located at path \u201c{0}\u201d. The reason is: {2} CanNotRead_1 = Cannot read \u201c{0}\u201d. +CanNotRead_3 = Cannot read \u201c{0}\u201d at line {1}, column {2}. CanNotReadPropertyInFile_2 = Cannot read property \u201c{1}\u201d in file \u201c{0}\u201d. +CanNotResolveAsAbsolutePath_1 = Cannot resolve \u201c{0}\u201d as an absolute path. CanNotRepresentInFormat_2 = Cannot represent \u201c{1}\u201d in a strictly standard-compliant {0} format. CanNotSetParameterValue_1 = Cannot set a value for parameter \u201c{0}\u201d. CanNotSetPropertyValue_1 = Cannot set a value for property \u201c{0}\u201d. @@ -69,7 +71,6 @@ EmptyArgument_1 = Argument \u2018{0}\u2019 shall not be empty. EmptyDictionary = The dictionary shall contain at least one entry. EmptyEnvelope2D = Envelope must be at least two-dimensional and non-empty. EmptyProperty_1 = Property named \u201c{0}\u201d shall not be empty. -ErrorIn_2 = Error in \u201c{0}\u201d: {1} ErrorInFileAtLine_2 = An error occurred in file \u201c{0}\u201d at line {1}. ExcessiveListSize_2 = A size of {1} elements is excessive for the \u201c{0}\u201d list. ExcessiveNumberOfDimensions_1 = For this algorithm, {0} is an excessive number of dimensions. diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties index c5cb2922ab..59f896eec0 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties @@ -39,7 +39,9 @@ CanNotParse_1 = Ne peut pas interpr\u00e9ter \u00ab\u202f{0} CanNotProcessProperty_2 = Ne peut pas traiter la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb pour la raison suivante\u00a0: {1} CanNotProcessPropertyAtPath_3 = Ne peut pas traiter la propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb d\u00e9sign\u00e9e par le chemin \u00ab\u202f{0}\u202f\u00bb pour la raison suivante\u00a0: {2} CanNotRead_1 = Ne peut pas lire \u00ab\u202f{0}\u202f\u00bb. +CanNotRead_3 = Ne peut pas lire \u00ab\u202f{0}\u202f\u00bb \u00e0 la ligne {1}, colonne {2}. CanNotReadPropertyInFile_2 = Ne peut pas lire la propri\u00e9t\u00e9 \u00ab\u202f{1}\u202f\u00bb dans le fichier \u00ab\u202f{0}\u202f\u00bb. +CanNotResolveAsAbsolutePath_1 = Ne peut pas r\u00e9soudre \u00ab\u202f{0}\u202f\u00bb comme un chemin absolu. CanNotRepresentInFormat_2 = Ne peut pas repr\u00e9senter \u00ab\u202f{1}\u202f\u00bb dans un format {0} strictement conforme. CanNotSetParameterValue_1 = Ne peut pas d\u00e9finir une valeur pour le param\u00e8tre \u00ab\u202f{0}\u202f\u00bb. CanNotSetPropertyValue_1 = Ne peut pas d\u00e9finir une valeur pour la propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb. @@ -66,7 +68,6 @@ EmptyArgument_1 = L\u2019argument \u2018{0}\u2019 ne doit pas EmptyDictionary = Le dictionnaire doit contenir au moins une entr\u00e9e. EmptyEnvelope2D = L\u2019enveloppe doit avoir au moins deux dimensions et ne pas \u00eatre vide. EmptyProperty_1 = La propri\u00e9t\u00e9 nomm\u00e9e \u00ab\u202f{0}\u202f\u00bb ne doit pas \u00eatre vide. -ErrorIn_2 = Erreur dans \u00ab\u202f{0}\u202f\u00bb\u00a0: {1} ErrorInFileAtLine_2 = Une erreur est survenue dans le fichier \u00ab\u202f{0}\u202f\u00bb \u00e0 la ligne {1}. ExcessiveListSize_2 = Une taille de {1} \u00e9l\u00e9ments est excessive pour la liste \u00ab\u202f{0}\u202f\u00bb. ExcessiveNumberOfDimensions_1 = Pour cet algorithme, {0} est un trop grand nombre de dimensions. diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/ResourceInternationalString.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/ResourceInternationalString.java index ca01478095..333e437107 100644 --- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/ResourceInternationalString.java +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/ResourceInternationalString.java @@ -113,10 +113,8 @@ public abstract class ResourceInternationalString extends AbstractInternationalS * @return a log record with the message of this international string. */ public final LogRecord toLogRecord(final Level level) { - final LogRecord record = new LogRecord(level, getKeyConstants().getKeyName(key)); final IndexedResourceBundle resources = getBundle(null); - record.setResourceBundleName(resources.getClass().getName()); - record.setResourceBundle(resources); + final LogRecord record = resources.getLogRecord(level, key); if (hasArguments) { record.setParameters(resources.toArray(arguments)); }