This is an automated email from the ASF dual-hosted git repository.
tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 1b888b03f2 TIKA-4921: locale followups (#3244)
1b888b03f2 is described below
commit 1b888b03f2485540bd4620c2d211edbf0351736b
Author: Tim Allison <[email protected]>
AuthorDate: Thu Sep 24 13:37:52 2026 -0400
TIKA-4921: locale followups (#3244)
---
.../tika/parser/ocr/TesseractOCRParserTest.java | 9 +-
.../org/apache/tika/parser/ogg/OggAudioParser.java | 170 ++++++++++++++++-----
.../org/apache/tika/parser/ogg/FlacParserTest.java | 3 -
.../tika/parser/ogg/VorbisCommentsLocaleTest.java | 76 +++++++++
.../apache/tika/parser/ocr/ImagePreprocessor.java | 2 +-
.../tika/pipes/ignite/IgniteConfigStore.java | 20 ++-
.../pipes/ignite/server/IgniteStoreServer.java | 12 +-
.../tika/pipes/ignite/IgniteConfigStoreTest.java | 25 ++-
8 files changed, 262 insertions(+), 55 deletions(-)
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/ocr/TesseractOCRParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/ocr/TesseractOCRParserTest.java
index 4685dacb14..9434b43971 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/ocr/TesseractOCRParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/parser/ocr/TesseractOCRParserTest.java
@@ -222,8 +222,6 @@ public class TesseractOCRParserTest extends TikaTest {
}
- // TODO TIKA-4923: metadata-extractor lowercases the resolution unit in
the default locale
- @DisabledIfSystemProperty(named = "user.language", matches = "tr")
@Test
public void getNormalMetadataToo() throws Exception {
//this should be successful whether or not TesseractOCR is
installed/active
@@ -258,6 +256,13 @@ public class TesseractOCRParserTest extends TikaTest {
m = getXML("testTIFF.tif").metadata;
assertEquals("100", m.get(TIFF.IMAGE_WIDTH));
assertEquals("75", m.get(TIFF.IMAGE_LENGTH));
+ }
+
+ // TODO TIKA-4923: metadata-extractor lowercases the resolution unit in
the default locale
+ @DisabledIfSystemProperty(named = "user.language", matches = "tr")
+ @Test
+ public void getNormalMetadataTooUnknownField() throws Exception {
+ Metadata m = getXML("testTIFF.tif").metadata;
assertEquals("72 dots per inch",
m.get(ImageMetadataExtractor.UNKNOWN_IMG_NS + "Exif IFD0:Y Resolution"));
}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
index 381469365c..4d13d4eabf 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
@@ -19,6 +19,7 @@ package org.apache.tika.parser.ogg;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
@@ -26,8 +27,13 @@ import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
import org.gagravarr.ogg.audio.OggAudioHeaders;
import org.gagravarr.ogg.audio.OggAudioInfoHeader;
@@ -82,9 +88,9 @@ public abstract class OggAudioParser extends AbstractParser {
* Returns the first positive integer found under the given comment keys,
* or null if there is none.
*/
- private static Integer firstPositiveInteger(VorbisStyleComments comments,
String... keys) {
+ private static Integer firstPositiveInteger(Map<String, List<String>>
fields, String... keys) {
for (String key : keys) {
- for (String value : comments.getComments(key)) {
+ for (String value : all(fields, key)) {
try {
int parsed = Integer.parseInt(value.trim());
if (parsed > 0) {
@@ -123,53 +129,60 @@ public abstract class OggAudioParser extends
AbstractParser {
protected static List<PictureBlock> extractComments(Metadata metadata,
XHTMLContentHandler xhtml, VorbisStyleComments comments)
throws IOException, TikaException, SAXException {
+ Map<String, List<String>> fields = fields(comments);
+ String title = first(fields, VorbisComments.KEY_TITLE);
+ String artist = first(fields, VorbisComments.KEY_ARTIST);
+ String album = first(fields, VorbisComments.KEY_ALBUM);
+ String trackNumber = first(fields, VorbisComments.KEY_TRACKNUMBER);
// Get the specific known comments
- metadata.set(TikaCoreProperties.TITLE, comments.getTitle());
- metadata.set(TikaCoreProperties.CREATOR, comments.getArtist());
- metadata.set(XMPDM.ARTIST, comments.getArtist());
- metadata.set(XMPDM.ALBUM, comments.getAlbum());
- metadata.set(XMPDM.GENRE, comments.getGenre());
- metadata.set(XMPDM.RELEASE_DATE, comments.getDate());
+ metadata.set(TikaCoreProperties.TITLE, title);
+ metadata.set(TikaCoreProperties.CREATOR, artist);
+ metadata.set(XMPDM.ARTIST, artist);
+ metadata.set(XMPDM.ALBUM, album);
+ metadata.set(XMPDM.GENRE, first(fields, VorbisComments.KEY_GENRE));
+ metadata.set(XMPDM.RELEASE_DATE, first(fields,
VorbisComments.KEY_DATE));
metadata.add(XMP.CREATOR_TOOL, comments.getVendor());
metadata.add(VORBIS_VENDOR, comments.getVendor());
//xmpDM:copyright is single-valued, so map the first comment; like
//vendor, the raw comments also stay available under the vorbis: name
- List<String> copyrights = comments.getComments("copyright");
- if (!copyrights.isEmpty()) {
- metadata.set(XMPDM.COPYRIGHT, copyrights.get(0));
+ String copyright = first(fields, "copyright");
+ if (copyright != null) {
+ metadata.set(XMPDM.COPYRIGHT, copyright);
}
- for (String comment : comments.getComments("comment")) {
+ for (String comment : all(fields, "comment")) {
metadata.add(XMPDM.LOG_COMMENT, comment);
}
// Grab the rest just in case; the pictures become embedded
// documents instead, their raw base64 blocks help nobody
- List<String> done = Arrays.asList(
+ Set<String> done = new HashSet<>();
+ for (String key : Arrays.asList(
VorbisComments.KEY_TITLE, VorbisComments.KEY_ARTIST,
VorbisComments.KEY_ALBUM, VorbisComments.KEY_GENRE,
VorbisComments.KEY_DATE, VorbisComments.KEY_TRACKNUMBER,
- "vendor", "comment", METADATA_BLOCK_PICTURE
- );
+ "vendor", "comment", METADATA_BLOCK_PICTURE)) {
+ done.add(fieldName(key));
+ }
// BAG: a Vorbis comment field can legitimately repeat.
- for (String key : comments.getAllComments().keySet()) {
- if (!done.contains(key)) {
- for (String value : comments.getAllComments().get(key)) {
- metadata.add(VORBIS, key, value);
+ for (Map.Entry<String, List<String>> field : fields.entrySet()) {
+ if (!done.contains(field.getKey())) {
+ for (String value : field.getValue()) {
+ metadata.add(VORBIS, field.getKey(), value);
}
}
}
// Output as text too
- xhtml.element("h1", comments.getTitle());
- xhtml.element("p", comments.getArtist());
+ xhtml.element("h1", title);
+ xhtml.element("p", artist);
// Album and Track number
- if (comments.getTrackNumber() != null) {
- xhtml.element("p", comments.getAlbum() + ", track " +
comments.getTrackNumber());
- metadata.set(Audio.RAW_TRACK_NUMBER, comments.getTrackNumber());
- NumberAndTotal trackNumberAndTotal =
NumberAndTotal.parse(comments.getTrackNumber());
+ if (trackNumber != null) {
+ xhtml.element("p", album + ", track " + trackNumber);
+ metadata.set(Audio.RAW_TRACK_NUMBER, trackNumber);
+ NumberAndTotal trackNumberAndTotal =
NumberAndTotal.parse(trackNumber);
if (trackNumberAndTotal != null) {
if (trackNumberAndTotal.number != null) {
metadata.set(XMPDM.TRACK_NUMBER,
trackNumberAndTotal.number);
@@ -179,9 +192,9 @@ public abstract class OggAudioParser extends AbstractParser
{
}
}
} else {
- xhtml.element("p", comments.getAlbum());
+ xhtml.element("p", album);
}
- for (String discValue : comments.getComments("discnumber")) {
+ for (String discValue : all(fields, "discnumber")) {
metadata.set(Audio.RAW_DISC_NUMBER, discValue);
NumberAndTotal discNumberAndTotal =
NumberAndTotal.parse(discValue);
if (discNumberAndTotal != null) {
@@ -194,24 +207,111 @@ public abstract class OggAudioParser extends
AbstractParser {
}
}
//explicit totals win over the combined "n/total" form
- Integer trackTotal = firstPositiveInteger(comments, "tracktotal",
"totaltracks");
+ Integer trackTotal = firstPositiveInteger(fields, "tracktotal",
"totaltracks");
if (trackTotal != null) {
metadata.set(Audio.TRACK_COUNT, trackTotal);
}
- Integer discTotal = firstPositiveInteger(comments, "disctotal",
"totaldiscs");
+ Integer discTotal = firstPositiveInteger(fields, "disctotal",
"totaldiscs");
if (discTotal != null) {
metadata.set(Audio.DISC_COUNT, discTotal);
}
// A few other bits
- xhtml.element("p", comments.getDate());
- for (String comment : comments.getComments("comment")) {
+ xhtml.element("p", first(fields, VorbisComments.KEY_DATE));
+ for (String comment : all(fields, "comment")) {
xhtml.element("p", comment);
}
- xhtml.element("p", comments.getGenre());
+ xhtml.element("p", first(fields, VorbisComments.KEY_GENRE));
// The pictures are the caller's to emit
- return parsePictures(comments);
+ return parsePictures(fields);
+ }
+
+ /**
+ * The comment fields keyed by lower-case name, re-read from the raw
comment header:
+ * vorbis-java folds names in the default locale and then strips
non-ASCII, so a
+ * Turkic JVM turns TITLE into "ttle" (TIKA-4921). Falls back to the
library's map
+ * when there is no raw header, as for comments built in code.
+ */
+ static Map<String, List<String>> fields(VorbisStyleComments comments) {
+ Map<String, List<String>> fields = rawFields(comments);
+ if (fields != null) {
+ return fields;
+ }
+ fields = new LinkedHashMap<>();
+ for (Map.Entry<String, List<String>> field :
comments.getAllComments().entrySet()) {
+ fields.computeIfAbsent(fieldName(field.getKey()), k -> new
ArrayList<>())
+ .addAll(field.getValue());
+ }
+ return fields;
+ }
+
+ private static Map<String, List<String>> rawFields(VorbisStyleComments
comments) {
+ byte[] data;
+ try {
+ data = comments.getData();
+ } catch (NullPointerException e) {
+ // no packet behind comments built in code
+ return null;
+ }
+ if (data == null) {
+ return null;
+ }
+ // the format-specific header size is protected; find the vendor
string instead
+ String vendor = comments.getVendor() == null ? "" :
comments.getVendor();
+ byte[] vendorBytes = vendor.getBytes(StandardCharsets.UTF_8);
+ ByteBuffer buffer =
ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+ int offset = -1;
+ for (int headerSize : new int[]{0, 4, 7, 8}) {
+ int end = headerSize + 4 + vendorBytes.length;
+ if (end <= data.length && buffer.getInt(headerSize) ==
vendorBytes.length &&
+ Arrays.equals(data, headerSize + 4, end, vendorBytes, 0,
vendorBytes.length)) {
+ offset = end;
+ break;
+ }
+ }
+ if (offset < 0) {
+ return null;
+ }
+ try {
+ buffer.position(offset);
+ int count = buffer.getInt();
+ if (count < 0) {
+ return null;
+ }
+ Map<String, List<String>> fields = new LinkedHashMap<>();
+ for (int i = 0; i < count; i++) {
+ int length = buffer.getInt();
+ if (length < 0 || length > buffer.remaining()) {
+ return null;
+ }
+ byte[] bytes = new byte[length];
+ buffer.get(bytes);
+ String comment = new String(bytes, StandardCharsets.UTF_8);
+ int eq = comment.indexOf('=');
+ if (eq < 0) {
+ continue;
+ }
+ fields.computeIfAbsent(fieldName(comment.substring(0, eq)), k
-> new ArrayList<>())
+ .add(comment.substring(eq + 1));
+ }
+ return fields;
+ } catch (BufferUnderflowException e) {
+ return null;
+ }
+ }
+
+ private static String fieldName(String name) {
+ return name.toLowerCase(Locale.ROOT);
+ }
+
+ private static String first(Map<String, List<String>> fields, String name)
{
+ List<String> values = fields.get(fieldName(name));
+ return values == null || values.isEmpty() ? null : values.get(0);
+ }
+
+ private static List<String> all(Map<String, List<String>> fields, String
name) {
+ return fields.getOrDefault(fieldName(name), Collections.emptyList());
}
/**
@@ -219,9 +319,9 @@ public abstract class OggAudioParser extends AbstractParser
{
* The pictures are carried as base64 encoded FLAC picture blocks;
* malformed blocks are skipped silently.
*/
- private static List<PictureBlock> parsePictures(VorbisStyleComments
comments) {
+ private static List<PictureBlock> parsePictures(Map<String, List<String>>
fields) {
List<PictureBlock> pictures = new ArrayList<>();
- for (String block : comments.getComments(METADATA_BLOCK_PICTURE)) {
+ for (String block : all(fields, METADATA_BLOCK_PICTURE)) {
byte[] decoded;
try {
decoded = Base64.getMimeDecoder().decode(block);
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java
index f4398d2307..4fde9e14ee 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/FlacParserTest.java
@@ -26,7 +26,6 @@ import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.io.TempDir;
import org.apache.tika.TikaTest;
@@ -89,8 +88,6 @@ public class FlacParserTest extends TikaTest {
* both sources are merged before the pick (both are front covers here,
* so the first one, from the comment, wins).
*/
- // TODO TIKA-4921: vorbis-java lowercases comment keys in the default
locale
- @DisabledIfSystemProperty(named = "user.language", matches = "tr")
@Test
public void testCommentAndNativePictureYieldOneThumbnail() throws
Exception {
List<Metadata> metadataList =
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisCommentsLocaleTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisCommentsLocaleTest.java
new file mode 100644
index 0000000000..e8a0b703b5
--- /dev/null
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/ogg/VorbisCommentsLocaleTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.tika.parser.ogg;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Locale;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.api.parallel.Isolated;
+
+import org.apache.tika.TikaTest;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.metadata.XMPDM;
+
+/**
+ * TIKA-4921: vorbis-java folds comment field names in the default locale, so
on a
+ * Turkic JVM "TITLE" is stored as "ttle" and never found again.
+ */
+// sets the JVM-wide default locale
+@Isolated
+public class VorbisCommentsLocaleTest extends TikaTest {
+
+ @Test
+ public void testTurkishLocale(@TempDir Path tmp) throws Exception {
+ // same-length upper-case keys keep every block header valid
+ String flac = new String(
+
Files.readAllBytes(getResourceAsFile("/test-documents/testFLAC_commentAndNativePicture.flac").toPath()),
+ StandardCharsets.ISO_8859_1);
+ assertEquals(1, count(flac, "title=Test Title"));
+ flac = flac.replace("title=Test Title", "TITLE=Test Title")
+ .replace("artist=Test Artist", "ARTIST=Test Artist");
+ Path upperCased = tmp.resolve("upper.flac");
+ Files.write(upperCased, flac.getBytes(StandardCharsets.ISO_8859_1));
+
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.forLanguageTag("tr-TR"));
+ try {
+ List<Metadata> metadataList = getRecursiveMetadata(upperCased);
+ assertEquals("Test Title",
metadataList.get(0).get(TikaCoreProperties.TITLE));
+ assertEquals("Test Artist", metadataList.get(0).get(XMPDM.ARTIST));
+ assertEquals(3, metadataList.size());
+ assertEquals("Comment cover",
metadataList.get(1).get(TikaCoreProperties.TITLE));
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ private static int count(String s, String needle) {
+ int n = 0;
+ for (int i = s.indexOf(needle); i >= 0; i = s.indexOf(needle, i + 1)) {
+ n++;
+ }
+ return n;
+ }
+}
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/ImagePreprocessor.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/ImagePreprocessor.java
index fdb4c42eff..f1b9fbdb7e 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/ImagePreprocessor.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-ocr-module/src/main/java/org/apache/tika/parser/ocr/ImagePreprocessor.java
@@ -125,7 +125,7 @@ class ImagePreprocessor implements Serializable {
angle = 0d;
} else {
metadata.add(TesseractOCRParser.IMAGE_ROTATION,
- String.format(Locale.getDefault(), "%.3f", angle));
+ String.format(Locale.ROOT, "%.3f", angle));
}
return angle;
diff --git
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
index e57bd62850..9e7c2909d2 100644
---
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
+++
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
@@ -17,6 +17,7 @@
package org.apache.tika.pipes.ignite;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
import org.apache.ignite.Ignite;
@@ -96,7 +97,7 @@ public class IgniteConfigStore implements ConfigStore {
.addresses("127.0.0.1:10800")
.build();
- Table table = ignite.tables().table(tableName);
+ Table table = ignite.tables().table(quotedName(tableName));
if (table == null) {
throw new IllegalStateException("Table " + tableName + " not
found. Ensure IgniteStoreServer is running.");
}
@@ -148,10 +149,11 @@ public class IgniteConfigStore implements ConfigStore {
public Set<String> keySet() {
checkInitialized();
try {
- var resultSet = ignite.sql().execute(null, "SELECT id FROM " +
tableName);
+ var resultSet = ignite.sql().execute(null, "SELECT id FROM " +
quotedName(tableName));
Set<String> keys = new HashSet<>();
while (resultSet.hasNext()) {
- keys.add(resultSet.next().stringValue("id"));
+ // by position: column lookup by name folds in the default
locale too
+ keys.add(resultSet.next().stringValue(0));
}
return keys;
} catch (Exception e) {
@@ -164,9 +166,9 @@ public class IgniteConfigStore implements ConfigStore {
public int size() {
checkInitialized();
try {
- var resultSet = ignite.sql().execute(null, "SELECT COUNT(*) as cnt
FROM " + tableName);
+ var resultSet = ignite.sql().execute(null, "SELECT COUNT(*) as cnt
FROM " + quotedName(tableName));
if (resultSet.hasNext()) {
- return (int) resultSet.next().longValue("cnt");
+ return (int) resultSet.next().longValue(0);
}
return 0;
} catch (Exception e) {
@@ -205,6 +207,14 @@ public class IgniteConfigStore implements ConfigStore {
this.tableName = tableName;
}
+ /**
+ * Quoted so Ignite does not fold it in the default locale (TIKA-4922);
upper-cased
+ * first so it still names a table created unquoted.
+ */
+ public static String quotedName(String tableName) {
+ return '"' + tableName.toUpperCase(Locale.ROOT).replace("\"", "\"\"")
+ '"';
+ }
+
public void setReplicas(int replicas) {
this.replicas = replicas;
}
diff --git
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
index 4b03922800..e467c56bf3 100644
---
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
+++
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
@@ -19,7 +19,6 @@ package org.apache.tika.pipes.ignite.server;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Locale;
import java.util.concurrent.ExecutionException;
import org.apache.ignite.IgniteServer;
@@ -28,6 +27,8 @@ import org.apache.ignite.table.Table;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.apache.tika.pipes.ignite.IgniteConfigStore;
+
/**
* Embedded Ignite 3.x server node that hosts the config store table.
* The {@link org.apache.tika.pipes.ignite.IgniteConfigStore} connects to this
node as a thin client.
@@ -116,19 +117,18 @@ public class IgniteStoreServer implements AutoCloseable {
node.api().sql().execute(null, createZoneSql);
LOG.info("Distribution zone 'tika_zone' created/verified");
- Table existingTable = node.api().tables().table(tableName);
+ Table existingTable =
node.api().tables().table(IgniteConfigStore.quotedName(tableName));
if (existingTable != null) {
LOG.info("Table {} already exists", tableName);
return;
}
- String createTableSql = String.format(Locale.ROOT,
- "CREATE TABLE IF NOT EXISTS %s (" +
+ String createTableSql = "CREATE TABLE IF NOT EXISTS " +
+ IgniteConfigStore.quotedName(tableName) + " (" +
" id VARCHAR PRIMARY KEY," +
" name VARCHAR," +
" json VARCHAR(10000)" +
- ") ZONE tika_zone",
- tableName);
+ ") ZONE tika_zone";
node.api().sql().execute(null, createTableSql);
LOG.info("Table {} created successfully", tableName);
diff --git
a/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
b/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
index bb31368ab3..2f2d6ce6b1 100644
---
a/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
+++
b/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Path;
import java.util.HashSet;
+import java.util.Locale;
import java.util.Set;
import org.junit.jupiter.api.AfterAll;
@@ -33,8 +34,8 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.api.parallel.Isolated;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,8 +45,8 @@ import org.apache.tika.plugins.ExtensionConfig;
/**
* Integration tests for {@link IgniteConfigStore} using an embedded Ignite
3.x server.
*/
-// TODO TIKA-4922: Ignite uppercases unquoted table names in the default locale
-@DisabledIfSystemProperty(named = "user.language", matches = "tr")
+// testTurkishLocale sets the JVM-wide default locale
+@Isolated
public class IgniteConfigStoreTest {
private static final Logger LOG =
LoggerFactory.getLogger(IgniteConfigStoreTest.class);
@@ -160,6 +161,24 @@ public class IgniteConfigStoreTest {
}
}
+ // TIKA-4922: Ignite folds unquoted table names in the default locale
+ @Test
+ public void testTurkishLocale() throws Exception {
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.forLanguageTag("tr-TR"));
+ IgniteConfigStore trStore = new IgniteConfigStore();
+ try {
+ trStore.init();
+ trStore.put("tr1", new ExtensionConfig("tr1", "type1", "{}"));
+ assertNotNull(trStore.get("tr1"));
+ assertTrue(trStore.keySet().contains("tr1"));
+ assertEquals(1, trStore.size());
+ } finally {
+ trStore.close();
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
@Test
public void testUninitializedStore() {
IgniteConfigStore uninitializedStore = new IgniteConfigStore();