diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java index a2f90bee17..405ab5fc58 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java @@ -89,8 +89,12 @@ public class LifecycleModuleBuilder // session may be different from rootSession seeded in DefaultMaven // explicitly seed the right session here to make sure it is used by Guice - sessionScope.enter( reactorContext.getSessionScopeMemento() ); - sessionScope.seed( MavenSession.class, session ); + final boolean scoped = session != rootSession; + if ( scoped ) + { + sessionScope.enter(); + sessionScope.seed( MavenSession.class, session ); + } try { @@ -144,7 +148,10 @@ public class LifecycleModuleBuilder } finally { - sessionScope.exit(); + if ( scoped ) + { + sessionScope.exit(); + } session.setCurrentProject( null ); diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java index 260dd25546..cf023c055e 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleStarter.java @@ -122,8 +122,7 @@ public class LifecycleStarter ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); ReactorBuildStatus reactorBuildStatus = new ReactorBuildStatus( session.getProjectDependencyGraph() ); reactorContext = - new ReactorContext( result, projectIndex, oldContextClassLoader, reactorBuildStatus, - sessionScope.memento() ); + new ReactorContext( result, projectIndex, oldContextClassLoader, reactorBuildStatus ); String builderId = session.getRequest().getBuilderId(); Builder builder = builders.get( builderId ); diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorContext.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorContext.java index 7df5314045..076c6229f8 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorContext.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/ReactorContext.java @@ -20,7 +20,6 @@ package org.apache.maven.lifecycle.internal; */ import org.apache.maven.execution.MavenExecutionResult; -import org.apache.maven.session.scope.internal.SessionScope; /** * Context that is fixed for the entire reactor build. @@ -40,17 +39,13 @@ public class ReactorContext private final ReactorBuildStatus reactorBuildStatus; - private final SessionScope.Memento sessionScope; - public ReactorContext( MavenExecutionResult result, ProjectIndex projectIndex, - ClassLoader originalContextClassLoader, ReactorBuildStatus reactorBuildStatus, - SessionScope.Memento sessionScope ) + ClassLoader originalContextClassLoader, ReactorBuildStatus reactorBuildStatus ) { this.result = result; this.projectIndex = projectIndex; this.originalContextClassLoader = originalContextClassLoader; this.reactorBuildStatus = reactorBuildStatus; - this.sessionScope = sessionScope; } public ReactorBuildStatus getReactorBuildStatus() @@ -73,11 +68,4 @@ public class ReactorContext return originalContextClassLoader; } - /** - * @since 3.3.0 - */ - public SessionScope.Memento getSessionScopeMemento() - { - return sessionScope; - } } diff --git a/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java b/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java index de97d4479f..8d30c10eff 100644 --- a/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java +++ b/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java @@ -19,35 +19,23 @@ package org.apache.maven.session.scope.internal; * under the License. */ -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; +import java.util.Collection; +import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import com.google.inject.Key; import com.google.inject.OutOfScopeException; import com.google.inject.Provider; import com.google.inject.Scope; -import com.google.inject.util.Providers; /** * SessionScope */ public class SessionScope - implements Scope + implements Scope { - /** - * @since 3.3.0 - */ - public static class Memento - { - final Map, Provider> seeded; - - Memento( final Map, Provider> seeded ) - { - this.seeded = Collections.unmodifiableMap( new HashMap<>( seeded ) ); - } - } private static final Provider SEEDED_KEY_PROVIDER = () -> { @@ -57,106 +45,99 @@ public class SessionScope /** * ScopeState */ - private static final class ScopeState + protected static final class ScopeState { - private final Map, Provider> seeded = new HashMap<>(); + private final Map, CachingProvider> provided = new ConcurrentHashMap<>(); - private final Map, Object> provided = new HashMap<>(); + public void seed( Class clazz, Provider value ) + { + provided.put( Key.get( clazz ), new CachingProvider<>( value ) ); + } + + @SuppressWarnings( "unchecked" ) + public Provider scope( Key key, Provider unscoped ) + { + Provider provider = provided.computeIfAbsent( key, k -> new CachingProvider<>( unscoped ) ); + return ( Provider ) provider; + } + + public Collection> providers() + { + return provided.values(); + } } - private final ThreadLocal> values = new ThreadLocal<>(); + private final List values = new CopyOnWriteArrayList<>(); public void enter() { - LinkedList stack = values.get(); - if ( stack == null ) - { - stack = new LinkedList<>(); - values.set( stack ); - } - stack.addFirst( new ScopeState() ); + values.add( 0, new ScopeState() ); } - /** - * @since 3.3.0 - */ - public void enter( Memento memento ) + protected ScopeState getScopeState() { - enter(); - getScopeState().seeded.putAll( memento.seeded ); - } - - private ScopeState getScopeState() - { - LinkedList stack = values.get(); - if ( stack == null || stack.isEmpty() ) + if ( values.isEmpty() ) { - throw new IllegalStateException(); + throw new OutOfScopeException( "Cannot access session scope outside of a scoping block" ); } - return stack.getFirst(); + return values.get( 0 ); } public void exit() { - final LinkedList stack = values.get(); - if ( stack == null || stack.isEmpty() ) + if ( values.isEmpty() ) { throw new IllegalStateException(); } - stack.removeFirst(); - if ( stack.isEmpty() ) - { - values.remove(); - } - } - - /** - * @since 3.3.0 - */ - public Memento memento() - { - LinkedList stack = values.get(); - return new Memento( stack != null ? stack.getFirst().seeded : Collections.emptyMap() ); + values.remove( 0 ); } public void seed( Class clazz, Provider value ) { - getScopeState().seeded.put( Key.get( clazz ), value ); + getScopeState().seed( clazz, value ); } public void seed( Class clazz, final T value ) { - getScopeState().seeded.put( Key.get( clazz ), Providers.of( value ) ); + seed( clazz, ( Provider ) () -> value ); } public Provider scope( final Key key, final Provider unscoped ) { - return () -> + // Lazy evaluating provider + return () -> getScopeState().scope( key, unscoped ).get(); + } + + protected static class CachingProvider implements Provider + { + private final Provider provider; + private volatile T value; + + CachingProvider( Provider provider ) { - LinkedList stack = values.get(); - if ( stack == null || stack.isEmpty() ) + this.provider = provider; + } + + public T value() + { + return value; + } + + @Override + public T get() + { + if ( value == null ) { - throw new OutOfScopeException( "Cannot access " + key + " outside of a scoping block" ); + synchronized ( this ) + { + if ( value == null ) + { + value = provider.get(); + } + } } - - ScopeState state = stack.getFirst(); - - Provider seeded = state.seeded.get( key ); - - if ( seeded != null ) - { - return (T) seeded.get(); - } - - T provided = (T) state.provided.get( key ); - if ( provided == null && unscoped != null ) - { - provided = unscoped.get(); - state.provided.put( key, provided ); - } - - return provided; - }; + return value; + } } @SuppressWarnings( { "unchecked" } ) diff --git a/maven-core/src/test/java/org/apache/maven/session/scope/internal/SessionScopeTest.java b/maven-core/src/test/java/org/apache/maven/session/scope/internal/SessionScopeTest.java new file mode 100644 index 0000000000..30874f4376 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/session/scope/internal/SessionScopeTest.java @@ -0,0 +1,81 @@ +package org.apache.maven.session.scope.internal; + +/* + * 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. + */ + +import javax.inject.Provider; + +import com.google.inject.Key; +import com.google.inject.OutOfScopeException; +import org.apache.maven.model.building.DefaultModelSourceTransformer; +import org.apache.maven.model.building.ModelSourceTransformer; +import org.apache.maven.model.locator.DefaultModelLocator; +import org.apache.maven.model.locator.ModelLocator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SessionScopeTest { + + @Test + public void testScope() throws Exception + { + SessionScope scope = new SessionScope(); + + assertThrows( OutOfScopeException.class, () -> scope.seed( ModelLocator.class, new DefaultModelLocator() ) ); + + Provider pml = scope.scope( Key.get( ModelLocator.class), DefaultModelLocator::new ); + assertNotNull( pml ); + assertThrows( OutOfScopeException.class, pml::get ); + + Provider pmst = scope.scope( Key.get( ModelSourceTransformer.class ), DefaultModelSourceTransformer::new ); + assertNotNull( pmst ); + + scope.enter(); + + final DefaultModelLocator dml1 = new DefaultModelLocator(); + scope.seed( ModelLocator.class, dml1 ); + + assertSame( dml1, pml.get() ); + + ModelSourceTransformer mst1 = pmst.get(); + assertSame( mst1, pmst.get() ); + Provider pmst1 = scope.scope( Key.get( ModelSourceTransformer.class ), DefaultModelSourceTransformer::new ); + assertNotNull( pmst1 ); + assertSame( mst1, pmst1.get() ); + + scope.enter(); + + pmst1 = scope.scope( Key.get( ModelSourceTransformer.class ), DefaultModelSourceTransformer::new ); + assertNotNull( pmst1 ); + assertNotSame( mst1, pmst1.get() ); + + scope.exit(); + + assertSame( mst1, pmst.get() ); + + scope.exit(); + + assertThrows( OutOfScopeException.class, pmst::get ); + assertThrows( OutOfScopeException.class, () -> scope.seed( ModelLocator.class, new DefaultModelLocator() ) ); + } +}