This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new c74b0a25af9 CAMEL-19936: camel-xml-io - Parsing error with line precise error (#11627) c74b0a25af9 is described below commit c74b0a25af97f7d7be03a824c8f594e528a5adb8 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Oct 2 16:34:33 2023 +0200 CAMEL-19936: camel-xml-io - Parsing error with line precise error (#11627) --- .../main/java/org/apache/camel/util/IOHelper.java | 28 ++++++++ .../java/org/apache/camel/xml/in/BaseParser.java | 25 +++++++ .../xml/io/XmlPullParserLocationException.java | 77 ++++++++++++++++++++++ .../org/apache/camel/xml/in/ModelParserTest.java | 21 ++++++ .../resources/invalid/convertBodyParseError.xml | 25 +++++++ 5 files changed, 176 insertions(+) diff --git a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java index e18d2168e46..c5892043305 100644 --- a/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java +++ b/core/camel-util/src/main/java/org/apache/camel/util/IOHelper.java @@ -475,6 +475,34 @@ public final class IOHelper { } } + /** + * Loads the entire stream into memory as a String and returns the given line number. + * <p/> + * Warning, don't use for crazy big streams :) + */ + public static String loadTextLine(InputStream in, int lineNumber) throws IOException { + int i = 0; + InputStreamReader isr = new InputStreamReader(in); + try { + BufferedReader reader = buffered(isr); + while (true) { + String line = reader.readLine(); + if (line != null) { + i++; + if (i >= lineNumber) { + return line; + } + } else { + break; + } + } + } finally { + close(isr, in); + } + + return null; + } + /** * Appends the text to the file. */ diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java index 1a181110fae..aa168ff15f5 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java @@ -49,6 +49,7 @@ import org.apache.camel.util.URISupport; import org.apache.camel.xml.io.MXParser; import org.apache.camel.xml.io.XmlPullParser; import org.apache.camel.xml.io.XmlPullParserException; +import org.apache.camel.xml.io.XmlPullParserLocationException; public class BaseParser { @@ -103,6 +104,30 @@ public class BaseParser { T definition, AttributeHandler<T> attributeHandler, ElementHandler<T> elementHandler, ValueHandler<T> valueHandler, boolean supportsExternalNamespaces) throws IOException, XmlPullParserException { + + try { + return doParseXml(definition, attributeHandler, elementHandler, valueHandler, supportsExternalNamespaces); + } catch (Exception e) { + if (e instanceof XmlPullParserLocationException) { + throw e; + } + // wrap in XmlPullParserLocationException so we have line-precise error + String msg = e.getMessage(); + Throwable cause = e; + if (e instanceof XmlPullParserException) { + if (e.getCause() != null) { + cause = e.getCause(); + msg = e.getCause().getMessage(); + } + } + throw new XmlPullParserLocationException(msg, resource, parser.getLineNumber(), parser.getColumnNumber(), cause); + } + } + + protected <T> T doParseXml( + T definition, AttributeHandler<T> attributeHandler, ElementHandler<T> elementHandler, ValueHandler<T> valueHandler, + boolean supportsExternalNamespaces) + throws IOException, XmlPullParserException { if (definition instanceof LineNumberAware) { // we want to get the line number where the tag starts (in case its multi-line) int line = parser.getStartLineNumber(); diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/io/XmlPullParserLocationException.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/io/XmlPullParserLocationException.java new file mode 100644 index 00000000000..ca7e21f9b35 --- /dev/null +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/io/XmlPullParserLocationException.java @@ -0,0 +1,77 @@ +/* + * 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. + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/ +// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/) +package org.apache.camel.xml.io; + +import org.apache.camel.spi.Resource; +import org.apache.camel.util.IOHelper; + +/** + * This exception is thrown to signal XML Pull Parser related faults. + */ +public class XmlPullParserLocationException extends RuntimeException { + private final int row; + private final int column; + private final Resource resource; + + public XmlPullParserLocationException(String s, Resource resource, int row, int column, Throwable cause) { + super(createMessage(s, resource, row, column, cause), cause); + this.row = row; + this.column = column; + this.resource = resource; + } + + public int getLineNumber() { + return row; + } + + public int getColumnNumber() { + return column; + } + + public Resource getResource() { + return resource; + } + + private static String createMessage(String s, Resource resource, int row, int column, Throwable cause) { + StringBuilder sb = new StringBuilder(); + sb.append(cause.getMessage()).append("\n"); + if (resource != null) { + sb.append("in ").append(resource.getLocation()).append(", line ") + .append(row).append(", column ").append(column) + .append(":\n"); + try { + String line = IOHelper.loadTextLine(resource.getInputStream(), row); + if (line != null) { + sb.append(line).append("\n"); + if (column > 1) { + String pad = " ".repeat(column - 2); + sb.append(pad); + } + sb.append("^\n"); + } + } catch (Exception e) { + // ignore + } + sb.append("\n"); + } + return sb.toString(); + } + +} diff --git a/core/camel-xml-io/src/test/java/org/apache/camel/xml/in/ModelParserTest.java b/core/camel-xml-io/src/test/java/org/apache/camel/xml/in/ModelParserTest.java index d5c68160ae9..51e8864348f 100644 --- a/core/camel-xml-io/src/test/java/org/apache/camel/xml/in/ModelParserTest.java +++ b/core/camel-xml-io/src/test/java/org/apache/camel/xml/in/ModelParserTest.java @@ -50,6 +50,9 @@ import org.apache.camel.model.rest.ParamDefinition; import org.apache.camel.model.rest.RestDefinition; import org.apache.camel.model.rest.RestsDefinition; import org.apache.camel.model.rest.VerbDefinition; +import org.apache.camel.spi.Resource; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.xml.io.XmlPullParserLocationException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -60,6 +63,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class ModelParserTest { @@ -420,6 +424,23 @@ public class ModelParserTest { assertEquals("myPolicy", dlc.getRedeliveryPolicyRef()); } + @Test + public void testParseError() throws Exception { + Path dir = getResourceFolder(); + Path path = new File(dir.toFile() + "/invalid", "convertBodyParseError.xml").toPath(); + Resource resource = ResourceHelper.fromString("file:convertBodyParseError.xml", Files.readString(path)); + try { + ModelParser parser = new ModelParser(resource, NAMESPACE); + parser.parseRoutesDefinition(); + fail("Should throw exception"); + } catch (XmlPullParserLocationException e) { + assertEquals(22, e.getLineNumber()); + assertEquals(25, e.getColumnNumber()); + assertEquals("file:convertBodyParseError.xml", e.getResource().getLocation()); + assertTrue(e.getMessage().startsWith("Unexpected attribute '{}ref'")); + } + } + private Path getResourceFolder() { String childFileString = getClass().getClassLoader().getResource("barInterceptorRoute.xml").getFile(); File parentFile = new File(childFileString).getParentFile(); diff --git a/core/camel-xml-io/src/test/resources/invalid/convertBodyParseError.xml b/core/camel-xml-io/src/test/resources/invalid/convertBodyParseError.xml new file mode 100644 index 00000000000..bf2c966f6bf --- /dev/null +++ b/core/camel-xml-io/src/test/resources/invalid/convertBodyParseError.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<routes id="camel" xmlns="http://camel.apache.org/schema/spring"> + <route> + <from ref="seda:a"/> + <convertBodyTo type="java.lang.Integer"/> + </route> +</routes>