This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24168 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4f1e7ebe632d09566e479ad2e90bf38a61b4fc42 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Jul 20 09:11:55 2026 +0200 CAMEL-24168: Fix BigDecimal precision handling when no explicit precision set Co-Authored-By: Claude Opus 4.6 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../src/main/docs/bindy-dataformat.adoc | 5 +- .../dataformat/bindy/annotation/DataField.java | 5 +- .../bindy/annotation/KeyValuePairField.java | 4 +- .../bindy/format/AbstractNumberFormat.java | 6 +- .../format/factories/BigDecimalFormatFactory.java | 8 +- .../bindy/csv/BindyBigDecimalNoPrecisionTest.java | 148 +++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 13 ++ 7 files changed, 178 insertions(+), 11 deletions(-) diff --git a/components/camel-bindy/src/main/docs/bindy-dataformat.adoc b/components/camel-bindy/src/main/docs/bindy-dataformat.adoc index 80e236ddf619..5e060e0c1415 100644 --- a/components/camel-bindy/src/main/docs/bindy-dataformat.adoc +++ b/components/camel-bindy/src/main/docs/bindy-dataformat.adoc @@ -483,7 +483,8 @@ use "default" to use platform default locale. the field in the CSV generated (output message) must be different compare to input position (pos). See the pos parameter. -| precision | int | | 0 | precision of the {@link java.math.BigDecimal} number to be created +| precision | int | | -1 a| precision of the {@link java.math.BigDecimal} number to be created. Use -1 (default) to preserve the original +scale from the input. | required | boolean | | false | Indicates if the field is mandatory @@ -1346,7 +1347,7 @@ to @Message.continueParseOnFailure. | position | int | | 0 a| Position of the field in the message generated - must be used when the position of the key/tag in the FIX message must be different -| precision | int | | 0 | precision of the BigDecimal number to be created +| precision | int | | -1 | precision of the BigDecimal number to be created. Use -1 (default) to preserve the original scale from the input. | required | boolean | | false | Indicates if the field is mandatory diff --git a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java index 4ef891393cd3..846cc6f0427c 100644 --- a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java +++ b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/DataField.java @@ -86,9 +86,10 @@ public @interface DataField { char paddingChar() default ' '; /** - * precision of the {@link java.math.BigDecimal} number to be created + * precision of the {@link java.math.BigDecimal} number to be created. Use -1 (default) to preserve the original + * scale from the input. */ - int precision() default 0; + int precision() default -1; /** * Position of the field in the output message generated (should start from 1). Must be used when the position of diff --git a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java index 87f2fc2cab26..e289c62296ed 100644 --- a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java +++ b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/annotation/KeyValuePairField.java @@ -70,11 +70,11 @@ public @interface KeyValuePairField { int position() default 0; /** - * precision of the BigDecimal number to be created + * precision of the BigDecimal number to be created. Use -1 (default) to preserve the original scale from the input. * * @return int */ - int precision() default 0; + int precision() default -1; /** * Indicates if the field is mandatory diff --git a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java index cd8e92515ce1..742761d00ef5 100644 --- a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java +++ b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/AbstractNumberFormat.java @@ -43,7 +43,7 @@ public abstract class AbstractNumberFormat<T extends Number> implements Format<T */ protected AbstractNumberFormat(boolean impliedDecimalPosition, int precision, Locale locale) { this.impliedDecimalPosition = impliedDecimalPosition; - this.precision = precision > 0 ? precision : 0; + this.precision = precision; this.format = null; this.multiplier = 1; @@ -58,8 +58,8 @@ public abstract class AbstractNumberFormat<T extends Number> implements Format<T if (this.impliedDecimalPosition) { this.format.setMinimumFractionDigits(0); this.format.setMaximumFractionDigits(0); - this.multiplier = Math.pow(10D, precision); - } else { + this.multiplier = Math.pow(10D, Math.max(precision, 0)); + } else if (this.precision >= 0) { this.format.setMinimumFractionDigits(this.precision); this.format.setMaximumFractionDigits(this.precision); } diff --git a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java index db0d2da62bc7..3a0a2236b214 100644 --- a/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java +++ b/components/camel-bindy/src/main/java/org/apache/camel/dataformat/bindy/format/factories/BigDecimalFormatFactory.java @@ -41,13 +41,17 @@ public class BigDecimalFormatFactory extends AbstractFormatFactory { return new BigDecimalFormat( formattingOptions.isImpliedDecimalSeparator(), formattingOptions.getPrecision(), + formattingOptions.getRounding(), formattingOptions.getLocale()); } private static class BigDecimalFormat extends AbstractNumberFormat<BigDecimal> { - BigDecimalFormat(boolean impliedDecimalPosition, int precision, Locale locale) { + private final String rounding; + + BigDecimalFormat(boolean impliedDecimalPosition, int precision, String rounding, Locale locale) { super(impliedDecimalPosition, precision, locale); + this.rounding = rounding; } @Override @@ -64,7 +68,7 @@ public class BigDecimalFormatFactory extends AbstractFormatFactory { result = result.divide(BigDecimal.valueOf(super.getMultiplier()), super.getPrecision(), RoundingMode.HALF_EVEN); } else { if (super.getPrecision() != -1) { - result = result.setScale(super.getPrecision()); + result = result.setScale(super.getPrecision(), RoundingMode.valueOf(rounding)); } } return result; diff --git a/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyBigDecimalNoPrecisionTest.java b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyBigDecimalNoPrecisionTest.java new file mode 100644 index 000000000000..8823edcd128c --- /dev/null +++ b/components/camel-bindy/src/test/java/org/apache/camel/dataformat/bindy/csv/BindyBigDecimalNoPrecisionTest.java @@ -0,0 +1,148 @@ +/* + * 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.camel.dataformat.bindy.csv; + +import java.math.BigDecimal; + +import org.apache.camel.EndpointInject; +import org.apache.camel.Exchange; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.dataformat.bindy.annotation.CsvRecord; +import org.apache.camel.dataformat.bindy.annotation.DataField; +import org.apache.camel.model.dataformat.BindyDataFormat; +import org.apache.camel.model.dataformat.BindyType; +import org.apache.camel.test.junit6.CamelTestSupport; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Tests that BigDecimal fields without an explicit precision attribute round-trip correctly (CAMEL-24168). + */ +public class BindyBigDecimalNoPrecisionTest extends CamelTestSupport { + + @Produce("direct:unmarshal") + private ProducerTemplate unmarshalTemplate; + + @EndpointInject("mock:unmarshal-result") + private MockEndpoint unmarshalResult; + + @Produce("direct:marshal") + private ProducerTemplate marshalTemplate; + + @EndpointInject("mock:marshal-result") + private MockEndpoint marshalResult; + + @Test + public void testUnmarshalBigDecimalWithoutPrecision() throws Exception { + unmarshalResult.expectedMessageCount(1); + + unmarshalTemplate.sendBody("10.35,foo"); + + unmarshalResult.assertIsSatisfied(); + + Exchange exchange = unmarshalResult.getReceivedExchanges().get(0); + NoPrecisionModel model = exchange.getIn().getBody(NoPrecisionModel.class); + assertNotNull(model); + assertEquals(new BigDecimal("10.35"), model.amount); + assertEquals("foo", model.label); + } + + @Test + public void testMarshalBigDecimalWithoutPrecision() throws Exception { + marshalResult.expectedMessageCount(1); + + NoPrecisionModel model = new NoPrecisionModel(); + model.amount = new BigDecimal("10.35"); + model.label = "foo"; + + marshalTemplate.sendBody(model); + + marshalResult.assertIsSatisfied(); + + String body = marshalResult.getReceivedExchanges().get(0).getIn().getBody(String.class); + assertEquals("10.35,foo\r\n", body); + } + + @Test + public void testUnmarshalBigDecimalWithExplicitPrecision() throws Exception { + unmarshalResult.expectedMessageCount(1); + + unmarshalTemplate.sendBody("10.355,bar"); + + unmarshalResult.assertIsSatisfied(); + + Exchange exchange = unmarshalResult.getReceivedExchanges().get(0); + NoPrecisionModel model = exchange.getIn().getBody(NoPrecisionModel.class); + assertNotNull(model); + // amount has no precision — original scale preserved + assertEquals(new BigDecimal("10.355"), model.amount); + } + + @Test + public void testUnmarshalIntegerValueBigDecimal() throws Exception { + unmarshalResult.expectedMessageCount(1); + + unmarshalTemplate.sendBody("10,bar"); + + unmarshalResult.assertIsSatisfied(); + + Exchange exchange = unmarshalResult.getReceivedExchanges().get(0); + NoPrecisionModel model = exchange.getIn().getBody(NoPrecisionModel.class); + assertNotNull(model); + assertEquals(new BigDecimal("10"), model.amount); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + BindyDataFormat unmarshalBindy = new BindyDataFormat() + .type(BindyType.Csv) + .classType(NoPrecisionModel.class) + .locale("en"); + + BindyDataFormat marshalBindy = new BindyDataFormat() + .type(BindyType.Csv) + .classType(NoPrecisionModel.class) + .locale("en"); + + from("direct:unmarshal") + .unmarshal(unmarshalBindy) + .to("mock:unmarshal-result"); + + from("direct:marshal") + .marshal(marshalBindy) + .to("mock:marshal-result"); + } + }; + } + + @CsvRecord(separator = ",") + public static class NoPrecisionModel { + @DataField(pos = 1) + public BigDecimal amount; + + @DataField(pos = 2) + public String label; + } +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc index 1fb3f0dadfe7..7731d7e7cc54 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc @@ -725,6 +725,19 @@ The page-blob `getRange` calculation has been corrected from `end - start` to `e Azure page blob ranges are inclusive on both ends. If you had a workaround adjusting the range values to compensate for this off-by-one, it should be removed. +=== camel-bindy - BigDecimal precision default changed + +The `@DataField.precision()` attribute default has changed from `0` to `-1` (meaning "unset"). +Previously, a `BigDecimal` field without an explicit `precision` would throw `ArithmeticException` +on unmarshal when the input had decimal places, and silently truncated decimals to zero on marshal. +With this fix, omitting `precision` preserves the original scale from the input value. + +The `@DataField.rounding()` attribute is now honored for `BigDecimal` fields that do not use a `pattern`. +Previously it was only applied when a `pattern` was specified. + +If you relied on the implicit `precision=0` behavior to round `BigDecimal` values to integers, +add `precision = 0` explicitly to your `@DataField` annotation. + === camel-xslt / camel-xslt-saxon - transformerFactoryConfigurationStrategy now honored The `transformerFactoryConfigurationStrategy` option is now applied on all factory creation paths.
