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 3cdfd5bd386c CAMEL-25303: camel-barcode - keep the text that
ISO-8859-1 cannot represent, and the hints added before the data format starts
(#27344)
3cdfd5bd386c is described below
commit 3cdfd5bd386c13c788b691381041ff87b592f27c
Author: allthingssecurity <[email protected]>
AuthorDate: Mon Oct 5 22:34:29 2026 +0530
CAMEL-25303: camel-barcode - keep the text that ISO-8859-1 cannot
represent, and the hints added before the data format starts (#27344)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../camel/catalog/docs/barcode-dataformat.adoc | 3 +-
.../src/main/docs/barcode-dataformat.adoc | 3 +-
.../dataformat/barcode/BarcodeDataFormat.java | 50 ++++++-
.../barcode/BarcodeDataFormatCharsetTest.java | 147 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 9 ++
5 files changed, 207 insertions(+), 5 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
index 6ec674dd920b..0e6798e72d1a 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
@@ -54,7 +54,7 @@ The default values are:
|height |100 px
-|encoding |UTF-8
+|encoding |ISO-8859-1 (the ZXing default), or UTF-8 when the text has
characters that ISO-8859-1 cannot represent. A `CHARACTER_SET` hint overrides
it. ZXing uses the encoding for QR-Code, Aztec and PDF417 (and for Data Matrix
only with the `DATA_MATRIX_COMPACT` hint).
|barcode format (BarcodeFormat) |QR-Code
|=======================================================================
@@ -75,6 +75,7 @@ your BarcodeDataFormat instance:
code.addToHintMap(DecodeHintType.TRY_HARDER, Boolean.true);
----
+The hints can be added before the data format is started (for example when the
route is configured).
For possible hints, please consult the xzing documentation.
=== Marshalling
diff --git a/components/camel-barcode/src/main/docs/barcode-dataformat.adoc
b/components/camel-barcode/src/main/docs/barcode-dataformat.adoc
index 6ec674dd920b..0e6798e72d1a 100644
--- a/components/camel-barcode/src/main/docs/barcode-dataformat.adoc
+++ b/components/camel-barcode/src/main/docs/barcode-dataformat.adoc
@@ -54,7 +54,7 @@ The default values are:
|height |100 px
-|encoding |UTF-8
+|encoding |ISO-8859-1 (the ZXing default), or UTF-8 when the text has
characters that ISO-8859-1 cannot represent. A `CHARACTER_SET` hint overrides
it. ZXing uses the encoding for QR-Code, Aztec and PDF417 (and for Data Matrix
only with the `DATA_MATRIX_COMPACT` hint).
|barcode format (BarcodeFormat) |QR-Code
|=======================================================================
@@ -75,6 +75,7 @@ your BarcodeDataFormat instance:
code.addToHintMap(DecodeHintType.TRY_HARDER, Boolean.true);
----
+The hints can be added before the data format is started (for example when the
route is configured).
For possible hints, please consult the xzing documentation.
=== Marshalling
diff --git
a/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
b/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
index b37c8ad8cda2..995cf47e6790 100644
---
a/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
+++
b/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
@@ -18,8 +18,11 @@ package org.apache.camel.dataformat.barcode;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.EnumMap;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.Set;
import javax.imageio.ImageIO;
@@ -77,6 +80,19 @@ public class BarcodeDataFormat extends ServiceSupport
implements DataFormat, Dat
*/
private final Map<DecodeHintType, Object> readerHintMap = new
EnumMap<>(DecodeHintType.class);
+ /**
+ * The hints added by the user, which are applied on top of the optimized
hints.
+ */
+ private final Map<EncodeHintType, Object> userWriterHintMap = new
EnumMap<>(EncodeHintType.class);
+ private final Map<DecodeHintType, Object> userReaderHintMap = new
EnumMap<>(DecodeHintType.class);
+
+ /**
+ * The hints removed by the user, which are removed from the optimized
hints (also a default hint removed before the
+ * data format is started).
+ */
+ private final Set<EncodeHintType> removedWriterHints =
EnumSet.noneOf(EncodeHintType.class);
+ private final Set<DecodeHintType> removedReaderHints =
EnumSet.noneOf(DecodeHintType.class);
+
/**
* Create instance with default parameters.
*/
@@ -170,6 +186,12 @@ public class BarcodeDataFormat extends ServiceSupport
implements DataFormat, Dat
// reader hints
this.readerHintMap.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
+
+ // the hints removed and added by the user
+ this.writerHintMap.keySet().removeAll(this.removedWriterHints);
+ this.readerHintMap.keySet().removeAll(this.removedReaderHints);
+ this.writerHintMap.putAll(this.userWriterHintMap);
+ this.readerHintMap.putAll(this.userReaderHintMap);
}
/**
@@ -187,13 +209,21 @@ public class BarcodeDataFormat extends ServiceSupport
implements DataFormat, Dat
// set values
final String type = this.params.getType().toString();
+ // ZXing writes the text in ISO-8859-1 unless a character set is
given, so use UTF-8 for a text that
+ // ISO-8859-1 cannot represent (ZXing then writes an ECI that tells
the reader the character set)
+ Map<EncodeHintType, Object> hints = writerHintMap;
+ if (!hints.containsKey(EncodeHintType.CHARACTER_SET) &&
!StandardCharsets.ISO_8859_1.newEncoder().canEncode(payload)) {
+ hints = new EnumMap<>(writerHintMap);
+ hints.put(EncodeHintType.CHARACTER_SET,
StandardCharsets.UTF_8.name());
+ }
+
// create code image
final BitMatrix matrix = writer.encode(
payload,
this.params.getFormat(),
this.params.getWidth(),
this.params.getHeight(),
- writerHintMap);
+ hints);
// write image back to stream
MatrixToImageWriter.writeToStream(matrix, type, stream);
@@ -227,6 +257,8 @@ public class BarcodeDataFormat extends ServiceSupport
implements DataFormat, Dat
*/
public final void addToHintMap(final EncodeHintType hintType, final Object
value) {
this.writerHintMap.put(hintType, value);
+ this.userWriterHintMap.put(hintType, value);
+ this.removedWriterHints.remove(hintType);
LOG.info("Added '{}' with value '{}' to writer hint map.", hintType,
value);
}
@@ -235,27 +267,39 @@ public class BarcodeDataFormat extends ServiceSupport
implements DataFormat, Dat
*/
public final void addToHintMap(final DecodeHintType hintType, final Object
value) {
this.readerHintMap.put(hintType, value);
+ this.userReaderHintMap.put(hintType, value);
+ this.removedReaderHints.remove(hintType);
}
/**
- * Removes a hint from writer (encode) hint map.
+ * Removes a hint from writer (encode) hint map. A hint removed before the
data format is started, such as a default
+ * hint, is removed when it starts.
*/
public final void removeFromHintMap(final EncodeHintType hintType) {
+ this.userWriterHintMap.remove(hintType);
+ this.removedWriterHints.add(hintType);
if (this.writerHintMap.containsKey(hintType)) {
this.writerHintMap.remove(hintType);
LOG.info("Removed '{}' from writer hint map.", hintType);
+ } else if (!isStarted()) {
+ LOG.info("Removing '{}' from writer hint map when the data format
starts.", hintType);
} else {
LOG.warn("Could not find encode hint type '{}' in writer hint
map.", hintType);
}
}
/**
- * Removes a hint from reader (decode) hint map.
+ * Removes a hint from reader (decode) hint map. A hint removed before the
data format is started, such as a default
+ * hint, is removed when it starts.
*/
public final void removeFromHintMap(final DecodeHintType hintType) {
+ this.userReaderHintMap.remove(hintType);
+ this.removedReaderHints.add(hintType);
if (this.readerHintMap.containsKey(hintType)) {
this.readerHintMap.remove(hintType);
LOG.info("Removed '{}' from reader hint map.", hintType);
+ } else if (!isStarted()) {
+ LOG.info("Removing '{}' from reader hint map when the data format
starts.", hintType);
} else {
LOG.warn("Could not find decode hint type '{}' in reader hint
map.", hintType);
}
diff --git
a/components/camel-barcode/src/test/java/org/apache/camel/dataformat/barcode/BarcodeDataFormatCharsetTest.java
b/components/camel-barcode/src/test/java/org/apache/camel/dataformat/barcode/BarcodeDataFormatCharsetTest.java
new file mode 100644
index 000000000000..64cd34d844ae
--- /dev/null
+++
b/components/camel-barcode/src/test/java/org/apache/camel/dataformat/barcode/BarcodeDataFormatCharsetTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.barcode;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import org.apache.camel.Exchange;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+/**
+ * The text of a barcode must come back as it was written, also when
ISO-8859-1 (the ZXing default) cannot represent it,
+ * and the hints added or removed before the data format is started must be
used.
+ */
+public class BarcodeDataFormatCharsetTest extends CamelTestSupport {
+
+ @Test
+ void testQRCodeJapaneseText() throws Exception {
+ assertEquals("日本語のテキスト", roundTrip(new BarcodeDataFormat(),
"日本語のテキスト"));
+ }
+
+ @Test
+ void testQRCodeCyrillicText() throws Exception {
+ assertEquals("Привет, мир", roundTrip(new BarcodeDataFormat(),
"Привет, мир"));
+ }
+
+ @Test
+ void testAztecCyrillicText() throws Exception {
+ assertEquals("Привет, мир",
+ roundTrip(new BarcodeDataFormat(200, 200,
BarcodeImageType.PNG, BarcodeFormat.AZTEC), "Привет, мир"));
+ }
+
+ @Test
+ void testPdf417CyrillicText() throws Exception {
+ assertEquals("Привет, мир",
+ roundTrip(new BarcodeDataFormat(400, 200,
BarcodeImageType.PNG, BarcodeFormat.PDF_417), "Привет, мир"));
+ }
+
+ @Test
+ void testQRCodeLatinText() throws Exception {
+ assertEquals("Hello Camel", roundTrip(new BarcodeDataFormat(), "Hello
Camel"));
+ assertEquals("Grüße aus Köln", roundTrip(new BarcodeDataFormat(),
"Grüße aus Köln"));
+ }
+
+ @Test
+ void testHintsAddedBeforeStart() throws Exception {
+ try (BarcodeDataFormat format = new BarcodeDataFormat()) {
+ format.addToHintMap(EncodeHintType.CHARACTER_SET, "UTF-8");
+ format.addToHintMap(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
+ format.start();
+
+ assertEquals("UTF-8",
format.getWriterHintMap().get(EncodeHintType.CHARACTER_SET));
+ assertEquals(ErrorCorrectionLevel.H,
format.getWriterHintMap().get(EncodeHintType.ERROR_CORRECTION));
+ assertEquals(Boolean.TRUE,
format.getReaderHintMap().get(DecodeHintType.PURE_BARCODE));
+ assertEquals(Boolean.TRUE,
format.getReaderHintMap().get(DecodeHintType.TRY_HARDER));
+ }
+ }
+
+ @Test
+ void testHintsRemovedBeforeStart() throws Exception {
+ BarcodeDataFormat format = new BarcodeDataFormat();
+ // default hints, and a hint added before start
+ format.removeFromHintMap(EncodeHintType.ERROR_CORRECTION);
+ format.removeFromHintMap(DecodeHintType.TRY_HARDER);
+ format.addToHintMap(EncodeHintType.MARGIN, 2);
+ format.removeFromHintMap(EncodeHintType.MARGIN);
+
+ Exchange exchange = new DefaultExchange(context);
+ assertEquals("Hello Camel", roundTrip(format, "Hello Camel",
exchange));
+
assertFalse(format.getWriterHintMap().containsKey(EncodeHintType.ERROR_CORRECTION));
+
assertFalse(format.getWriterHintMap().containsKey(EncodeHintType.MARGIN));
+
assertFalse(format.getReaderHintMap().containsKey(DecodeHintType.TRY_HARDER));
+ // the QR code was written with the ZXing default error correction
(L), not the default hint (H)
+ assertEquals("L",
exchange.getMessage().getHeader("ERROR_CORRECTION_LEVEL"));
+
+ // a hint added again after it was removed is used
+ format.addToHintMap(EncodeHintType.ERROR_CORRECTION,
ErrorCorrectionLevel.M);
+ exchange = new DefaultExchange(context);
+ assertEquals("Hello Camel", roundTrip(format, "Hello Camel",
exchange));
+ assertEquals("M",
exchange.getMessage().getHeader("ERROR_CORRECTION_LEVEL"));
+ }
+
+ @Test
+ void testDefaultHintsWithoutRemoval() throws Exception {
+ Exchange exchange = new DefaultExchange(context);
+ assertEquals("Hello Camel", roundTrip(new BarcodeDataFormat(), "Hello
Camel", exchange));
+ assertEquals("H",
exchange.getMessage().getHeader("ERROR_CORRECTION_LEVEL"));
+ }
+
+ @Test
+ void testCharacterSetHintIsUsed() throws Exception {
+ BarcodeDataFormat format = new BarcodeDataFormat();
+ format.addToHintMap(EncodeHintType.CHARACTER_SET, "UTF-8");
+
+ Exchange exchange = new DefaultExchange(context);
+ assertEquals("Grüße", roundTrip(format, "Grüße", exchange));
+ // the text was written in UTF-8 as the hint asks, not in ISO-8859-1
+ List<?> segments = exchange.getMessage().getHeader("BYTE_SEGMENTS",
List.class);
+ assertEquals(1, segments.size());
+ assertArrayEquals("Grüße".getBytes(StandardCharsets.UTF_8), (byte[])
segments.get(0));
+ }
+
+ private String roundTrip(BarcodeDataFormat writer, String text) throws
Exception {
+ return roundTrip(writer, text, new DefaultExchange(context));
+ }
+
+ private String roundTrip(BarcodeDataFormat writer, String text, Exchange
exchange) throws Exception {
+ try (BarcodeDataFormat reader = new BarcodeDataFormat()) {
+ writer.start();
+ reader.start();
+
+ ByteArrayOutputStream image = new ByteArrayOutputStream();
+ writer.marshal(exchange, text, image);
+ return (String) reader.unmarshal(exchange,
+ new BufferedInputStream(new
ByteArrayInputStream(image.toByteArray())));
+ } finally {
+ writer.stop();
+ }
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 2170298d9585..8313a4b93338 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -755,6 +755,15 @@ data format also writes the message with the charset of
the exchange when it mar
US-ASCII messages are parsed as before. A route that repaired the wrongly
decoded text itself must stop
doing so.
+=== camel-barcode - text outside ISO-8859-1
+
+The barcode data format wrote the text in ISO-8859-1 (the ZXing default), so
every character that ISO-8859-1 cannot
+represent (such as Japanese, Cyrillic, Greek or an emoji) was written as `?`.
Such a text is now written in UTF-8,
+with an ECI segment that tells the reader the character set. A reader without
ECI support sees the UTF-8 bytes
+instead of the `?` characters, so it must decode them as UTF-8. Text that
ISO-8859-1 can represent is written as
+before, byte for byte, and a `CHARACTER_SET` hint takes precedence. The
documented default encoding is now
+"ISO-8859-1, or UTF-8 when needed" instead of "UTF-8".
+
=== camel-bindy - fixed-length records with characters outside the BMP
Since Camel 3.1 a fixed-length record is read by counting code points (or
graphemes with