diff --git a/src/it/test_reactor_test_scoped_dependency/pom.xml b/src/it/test_reactor_test_scoped_dependency/pom.xml
index 79697f9..5e5f728 100644
--- a/src/it/test_reactor_test_scoped_dependency/pom.xml
+++ b/src/it/test_reactor_test_scoped_dependency/pom.xml
@@ -14,7 +14,7 @@
pom
Test Reactor Test-Scoped Dependencies
- Reproduces homesplice scenario: test-scoped dependency on test library
+ Reproduces test-scoped dependency on test library scenario
test-lib
diff --git a/src/main/java/org/scoverage/plugin/SCoverageAggregationCoordinator.java b/src/main/java/org/scoverage/plugin/SCoverageAggregationCoordinator.java
index daeddb5..052bd01 100644
--- a/src/main/java/org/scoverage/plugin/SCoverageAggregationCoordinator.java
+++ b/src/main/java/org/scoverage/plugin/SCoverageAggregationCoordinator.java
@@ -17,86 +17,90 @@
package org.scoverage.plugin;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
-import org.apache.maven.execution.MavenSession;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.SessionData;
/**
* Coordinates aggregated coverage report generation in multi-threaded Maven builds.
*
- * Uses MavenSession request data to store state, which works across different plugin classloaders.
- * Stores only standard Java types (ConcurrentHashMap, AtomicBoolean) that are loaded by the system classloader.
+ * Uses RepositorySystemSession.getData() which provides build-global, thread-safe storage
+ * that works across different plugin classloaders and all modules in the reactor.
+ *
+ * Only stores standard Java types (ConcurrentHashMap, AtomicBoolean) loaded by the bootstrap classloader
+ * to avoid ClassCastException across different plugin classloaders.
*
* Ensures that:
*
* - Only one thread (the last module) performs the aggregation
* - Other threads skip aggregation and continue
*
+ *
+ * Implementation Note: Uses synchronized block with SessionData.get()/set() instead of computeIfAbsent()
+ * for backward compatibility with Maven 3.6.3+ (computeIfAbsent was added in Maven Resolver 1.9.x / Maven 3.9.x).
*/
public class SCoverageAggregationCoordinator
{
- private static final String REMAINING_MODULES_KEY = "scoverage-remaining-modules";
- private static final String CLAIMED_KEY = "scoverage-claimed";
+ private static final String REMAINING_MODULES_KEY = SCoverageAggregationCoordinator.class.getName() + ".remainingModules";
+ private static final String CLAIMED_KEY = SCoverageAggregationCoordinator.class.getName() + ".claimed";
/**
* Notifies the coordinator that this module has completed testing and determines if it should perform aggregation.
* Only the last module to complete (in a parallel build) will return true, and only once.
*
- * @param session Maven session
+ * @param repositorySession Repository system session providing build-global storage
* @param moduleId unique identifier for the module (e.g., groupId:artifactId)
- * @param expectedModuleIds set of expected module IDs (auto-registers session if needed)
+ * @param expectedModuleIds set of expected module IDs (auto-registers build if needed)
* @return true if this module should perform aggregation, false otherwise
*/
- public static boolean shouldPerformAggregation( MavenSession session, String moduleId, Set expectedModuleIds )
+ @SuppressWarnings("unchecked")
+ public static boolean shouldPerformAggregation( RepositorySystemSession repositorySession, String moduleId, Set expectedModuleIds )
{
- // Get shared data map from Maven session - this is shared across all plugin classloaders
- Map sessionData = session.getRequest().getData();
+ SessionData sessionData = repositorySession.getData();
- // Initialize remaining modules set and claimed flag (thread-safe, only once per session)
- Set remainingModules = getOrCreate( sessionData, REMAINING_MODULES_KEY, () -> {
- Set modules = ConcurrentHashMap.newKeySet();
- modules.addAll( expectedModuleIds );
- return modules;
- } );
+ // Initialize data structures using backward-compatible get/set API
+ // (computeIfAbsent was added in Maven Resolver 1.9.x / Maven 3.9.x)
+ Set remainingModules;
+ AtomicBoolean claimed;
+
+ // Use SessionData itself as synchronization lock for initialization
+ // SessionData is build-global and thread-safe, so it's safe to synchronize on
+ synchronized ( sessionData )
+ {
+ // Get or create remaining modules set
+ remainingModules = (Set) sessionData.get( REMAINING_MODULES_KEY );
+ if ( remainingModules == null )
+ {
+ remainingModules = ConcurrentHashMap.newKeySet();
+ remainingModules.addAll( expectedModuleIds );
+ sessionData.set( REMAINING_MODULES_KEY, remainingModules );
+ }
- AtomicBoolean claimed = getOrCreate( sessionData, CLAIMED_KEY, () -> new AtomicBoolean( false ) );
+ // Get or create claimed flag
+ claimed = (AtomicBoolean) sessionData.get( CLAIMED_KEY );
+ if ( claimed == null )
+ {
+ claimed = new AtomicBoolean( false );
+ sessionData.set( CLAIMED_KEY, claimed );
+ }
+ }
// Remove this module from the remaining set
remainingModules.remove( moduleId );
// Return true only if this is the last module and nobody else has claimed aggregation
- return remainingModules.isEmpty() && claimed.compareAndSet( false, true );
- }
+ boolean result = remainingModules.isEmpty() && claimed.compareAndSet( false, true );
- /**
- * Gets an object from the session data map, or creates it if absent using double-checked locking.
- * This ensures thread-safe initialization without unnecessary synchronization on subsequent calls.
- *
- * @param sessionData the shared session data map
- * @param key the key to look up
- * @param supplier function to create the value if absent
- * @return the value from the map (existing or newly created)
- */
- @SuppressWarnings("unchecked")
- private static T getOrCreate( Map sessionData, String key, Supplier supplier )
- {
- Object value = sessionData.get( key );
- if ( value == null )
+ // Clean up after aggregation is claimed
+ if ( result )
{
- synchronized ( sessionData )
- {
- value = sessionData.get( key );
- if ( value == null )
- {
- value = supplier.get();
- sessionData.put( key, value );
- }
- }
+ sessionData.set( REMAINING_MODULES_KEY, null );
+ sessionData.set( CLAIMED_KEY, null );
}
- return (T) value;
+
+ return result;
}
}
diff --git a/src/main/java/org/scoverage/plugin/SCoverageReportMojo.java b/src/main/java/org/scoverage/plugin/SCoverageReportMojo.java
index 0732777..fda81bb 100644
--- a/src/main/java/org/scoverage/plugin/SCoverageReportMojo.java
+++ b/src/main/java/org/scoverage/plugin/SCoverageReportMojo.java
@@ -49,6 +49,8 @@
import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.eclipse.aether.RepositorySystemSession;
+
import org.codehaus.plexus.util.StringUtils;
import scala.Option;
@@ -140,6 +142,12 @@ public class SCoverageReportMojo
@Parameter(defaultValue = "${session}", readonly = true, required = true)
private MavenSession session;
+ /**
+ * Repository system session for build-global storage.
+ */
+ @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true)
+ private RepositorySystemSession repositorySystemSession;
+
/**
* All Maven projects in the reactor.
*/
@@ -613,7 +621,7 @@ private void tryGenerateAggregatedReport()
String moduleId = project.getGroupId() + ":" + project.getArtifactId();
Set expectedModuleIds = getExpectedModuleIds();
- boolean shouldAggregate = SCoverageAggregationCoordinator.shouldPerformAggregation( session, moduleId, expectedModuleIds );
+ boolean shouldAggregate = SCoverageAggregationCoordinator.shouldPerformAggregation( repositorySystemSession, moduleId, expectedModuleIds );
if ( shouldAggregate )
{