From 8817c17e5cfce17bca8719cf80aa180323398361 Mon Sep 17 00:00:00 2001 From: "Matteo Franci a.k.a. Fugerit" Date: Mon, 22 Sep 2025 18:09:12 +0200 Subject: [PATCH 1/4] fix(thread): FWK005 parse may not be called while parsing on EventModelParser.parse() This is a draft fix for issue : https://issues.apache.org/jira/browse/FOP-3275 It does not fix all the build error --- .../fop/events/model/EventModelParser.java | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java index 7d3b7a03c4e..6b517929604 100644 --- a/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java +++ b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java @@ -20,6 +20,7 @@ package org.apache.fop.events.model; import java.util.Stack; +import java.util.concurrent.*; import javax.xml.transform.Source; import javax.xml.transform.Transformer; @@ -57,16 +58,38 @@ private EventModelParser() { * @return the created event model structure * @throws TransformerException if an error occurs while parsing the XML file */ - public static EventModel parse(Source src) - throws TransformerException { - Transformer transformer = tFactory.newTransformer(); - transformer.setErrorListener(new DefaultErrorListener(LOG)); - + public static EventModel parse(Source src) throws TransformerException { EventModel model = new EventModel(); - SAXResult res = new SAXResult(getContentHandler(model)); - - transformer.transform(src, res); - return model; + // Create a single-thread executor for this parsing operation + ExecutorService executor = Executors.newSingleThreadExecutor(); + try ( AutoCloseable executorAc = () -> executor.shutdown() ) { + // Submit the parsing task and wait for completion + Future parsingTask = executor.submit(() -> { + try { + Transformer transformer = tFactory.newTransformer(); + transformer.setErrorListener(new DefaultErrorListener(LOG)); + SAXResult res = new SAXResult(getContentHandler(model)); + transformer.transform(src, res); + return null; + } catch (TransformerException e) { + throw new RuntimeException(e); + } + }); + // Block until parsing is complete + parsingTask.get(); + return model; + } catch (ExecutionException e) { + if (e.getCause() instanceof RuntimeException && + e.getCause().getCause() instanceof TransformerException) { + throw (TransformerException) e.getCause().getCause(); + } + throw new TransformerException(e.getCause()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new TransformerException("Parsing was interrupted", e); + } catch (Exception e) { + throw new TransformerException("Parsing generic error", e); + } } /** From 978a1b0d67fd3be1b28b42a4279196c414565c13 Mon Sep 17 00:00:00 2001 From: "Matteo Franci a.k.a. Fugerit" Date: Mon, 22 Sep 2025 18:25:50 +0200 Subject: [PATCH 2/4] chore(optimize): different behaviour for java 25+ Older version of java will use the previous behavior. This is a draft fix for issue : https://issues.apache.org/jira/browse/FOP-3275 --- .../fop/events/model/EventModelParser.java | 95 +++++++++++++------ 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java index 6b517929604..217a0bb57ae 100644 --- a/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java +++ b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java @@ -21,6 +21,7 @@ import java.util.Stack; import java.util.concurrent.*; +import java.util.function.Function; import javax.xml.transform.Source; import javax.xml.transform.Transformer; @@ -52,6 +53,68 @@ private EventModelParser() { private static SAXTransformerFactory tFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + abstract static class EventModelParserWorker { + public abstract EventModel parse(Source src) throws TransformerException; + } + + private static EventModelParserWorker init() { + String javaVersion = System.getProperty("java.version"); + if ( javaVersion.compareTo( "25" ) < 0 ) { + // preserve the classic fop parsing behaviour on java 24 or less + return new EventModelParserWorker() { + @Override + public EventModel parse(Source src) throws TransformerException { + Transformer transformer = tFactory.newTransformer(); + transformer.setErrorListener(new DefaultErrorListener(LOG)); + EventModel model = new EventModel(); + SAXResult res = new SAXResult(getContentHandler(model)); + transformer.transform(src, res); + return model; + } + }; + } else { + return new EventModelParserWorker() { + // new parsing behaviour on java 25+ (independent thread) + @Override + public EventModel parse(Source src) throws TransformerException { + EventModel model = new EventModel(); + // Create a single-thread executor for this parsing operation + ExecutorService executor = Executors.newSingleThreadExecutor(); + try ( AutoCloseable executorAc = () -> executor.shutdown() ) { + // Submit the parsing task and wait for completion + Future parsingTask = executor.submit(() -> { + try { + Transformer transformer = tFactory.newTransformer(); + transformer.setErrorListener(new DefaultErrorListener(LOG)); + SAXResult res = new SAXResult(getContentHandler(model)); + transformer.transform(src, res); + return null; + } catch (TransformerException e) { + throw new RuntimeException(e); + } + }); + // Block until parsing is complete + parsingTask.get(); + return model; + } catch (ExecutionException e) { + if (e.getCause() instanceof RuntimeException && + e.getCause().getCause() instanceof TransformerException) { + throw (TransformerException) e.getCause().getCause(); + } + throw new TransformerException(e.getCause()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new TransformerException("Parsing was interrupted", e); + } catch (Exception e) { + throw new TransformerException("Parsing generic error", e); + } + } + }; + } + } + + private static final EventModelParserWorker PARSER_WORKER = init(); + /** * Parses an event model file into an EventModel instance. * @param src the Source instance pointing to the XML file @@ -59,37 +122,7 @@ private EventModelParser() { * @throws TransformerException if an error occurs while parsing the XML file */ public static EventModel parse(Source src) throws TransformerException { - EventModel model = new EventModel(); - // Create a single-thread executor for this parsing operation - ExecutorService executor = Executors.newSingleThreadExecutor(); - try ( AutoCloseable executorAc = () -> executor.shutdown() ) { - // Submit the parsing task and wait for completion - Future parsingTask = executor.submit(() -> { - try { - Transformer transformer = tFactory.newTransformer(); - transformer.setErrorListener(new DefaultErrorListener(LOG)); - SAXResult res = new SAXResult(getContentHandler(model)); - transformer.transform(src, res); - return null; - } catch (TransformerException e) { - throw new RuntimeException(e); - } - }); - // Block until parsing is complete - parsingTask.get(); - return model; - } catch (ExecutionException e) { - if (e.getCause() instanceof RuntimeException && - e.getCause().getCause() instanceof TransformerException) { - throw (TransformerException) e.getCause().getCause(); - } - throw new TransformerException(e.getCause()); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new TransformerException("Parsing was interrupted", e); - } catch (Exception e) { - throw new TransformerException("Parsing generic error", e); - } + return PARSER_WORKER.parse(src); } /** From 3ce780307d797a28eab1ffd6e5c779b1fe924bdb Mon Sep 17 00:00:00 2001 From: "Matteo Franci a.k.a. Fugerit" Date: Mon, 22 Sep 2025 18:53:01 +0200 Subject: [PATCH 3/4] chore(optimize): more robust version parsing This is a draft fix for issue : https://issues.apache.org/jira/browse/FOP-3275 --- .../fop/events/model/EventModelParser.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java index 217a0bb57ae..2b8bc29a2ac 100644 --- a/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java +++ b/fop-events/src/main/java/org/apache/fop/events/model/EventModelParser.java @@ -57,9 +57,33 @@ abstract static class EventModelParserWorker { public abstract EventModel parse(Source src) throws TransformerException; } + private static int parseJavaMajorVersion() { + String javaVersion = System.getProperty("java.version"); + String[] parts = javaVersion.split("\\."); + if (parts[0].equals("1")) { + // Java 8 and below use 1.x format + return Integer.parseInt(parts[1]); + } else { + // Java 9+ use direct major version + return Integer.parseInt(parts[0]); + } + } + + private static boolean isClassicBehaviour( int majorVersion ) { + try { + + boolean useClassicParser = majorVersion < 25; + LOG.debug( String.format( "Java major version: %s, using %s parser", + majorVersion, useClassicParser ? "classic" : "thread-isolated") ); + return useClassicParser; + } catch (Exception e) { + LOG.debug("Error detecting Java version, defaulting to thread-isolated parser", e); + return false; + } + } + private static EventModelParserWorker init() { - String javaVersion = System.getProperty("java.version"); - if ( javaVersion.compareTo( "25" ) < 0 ) { + if ( isClassicBehaviour( parseJavaMajorVersion() ) ) { // preserve the classic fop parsing behaviour on java 24 or less return new EventModelParserWorker() { @Override From 12d3dad1a63459fbc3ae76bc11a056138d1e9cb6 Mon Sep 17 00:00:00 2001 From: "Matteo Franci a.k.a. Fugerit" Date: Tue, 23 Sep 2025 08:56:03 +0200 Subject: [PATCH 4/4] fix(test): new File("").exists() on java 25 return true We just use another non-existent path name 'not-exists' --- .../org/apache/fop/fonts/autodetect/FontFileFinderTestCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fop-core/src/test/java/org/apache/fop/fonts/autodetect/FontFileFinderTestCase.java b/fop-core/src/test/java/org/apache/fop/fonts/autodetect/FontFileFinderTestCase.java index ab9e587d219..39d24ccb397 100644 --- a/fop-core/src/test/java/org/apache/fop/fonts/autodetect/FontFileFinderTestCase.java +++ b/fop-core/src/test/java/org/apache/fop/fonts/autodetect/FontFileFinderTestCase.java @@ -81,7 +81,7 @@ public void testValidEventListener() throws IOException { FontEventListener mockListener = mock(FontEventListener.class); FontFileFinder finder = new FontFileFinder(mockListener); - finder.find(new File("")); + finder.find(new File("not-exists")); verify(mockListener, times(1)).fontDirectoryNotFound(any(), any()); }