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 548fe6c8ffd6..1cbaf5334606 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 @@ -90,8 +90,12 @@ public void buildProject( MavenSession session, MavenSession rootSession, Reacto // 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 { @@ -145,7 +149,10 @@ public void buildProject( MavenSession session, MavenSession rootSession, Reacto } 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 cee80739234d..834498126080 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 @@ -107,8 +107,7 @@ public void execute( MavenSession session ) 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 7df531404520..076c6229f8ef 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 @@ */ 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 ClassLoader getOriginalContextClassLoader() 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 ac423bc6c92c..1c9096b8703d 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,16 +19,16 @@ * under the License. */ -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +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 @@ -36,18 +36,6 @@ public class SessionScope 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 = new Provider() { @@ -60,110 +48,123 @@ public Object get() /** * ScopeState */ - private static final class ScopeState + protected static final class ScopeState { - private final Map, Provider> seeded = new HashMap<>(); + private final ConcurrentMap, 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 ) ); + } - private final ThreadLocal> values = new ThreadLocal<>(); + @SuppressWarnings( "unchecked" ) + public Provider scope(Key key, final Provider unscoped ) + { + Provider provider = provided.get( key ); + if ( provider == null ) + { + CachingProvider newValue = new CachingProvider<>( unscoped ); + provider = provided.putIfAbsent( key, newValue ); + if ( provider == null ) + { + provider = newValue; + } + } + return ( Provider ) provider; + } - public void enter() - { - LinkedList stack = values.get(); - if ( stack == null ) + public Collection> providers() { - stack = new LinkedList<>(); - values.set( stack ); + return provided.values(); } - stack.addFirst( new ScopeState() ); + } - /** - * @since 3.3.0 - */ - public void enter( Memento memento ) + private final List values = new CopyOnWriteArrayList<>(); + + public void enter() { - enter(); - getScopeState().seeded.putAll( memento.seeded ); + values.add( 0, new ScopeState() ); } - private ScopeState getScopeState() + protected 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., Provider>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, new Provider() + { + @Override + public T get() + { + return value; + } + } ); } public Provider scope( final Key key, final Provider unscoped ) { + // Lazy evaluating provider return new Provider() { - @SuppressWarnings( "unchecked" ) + @Override public T get() { - LinkedList stack = values.get(); - if ( stack == null || stack.isEmpty() ) - { - throw new OutOfScopeException( "Cannot access " + key + " outside of a scoping block" ); - } + return getScopeState().scope( key, unscoped ).get(); + } + }; + } - ScopeState state = stack.getFirst(); + protected static class CachingProvider implements Provider + { + private final Provider provider; + private volatile T value; - Provider seeded = state.seeded.get( key ); + CachingProvider( Provider provider ) + { + this.provider = provider; + } - if ( seeded != null ) - { - return (T) seeded.get(); - } + public T value() + { + return value; + } - T provided = (T) state.provided.get( key ); - if ( provided == null && unscoped != null ) + @Override + public T get() + { + if ( value == null ) + { + synchronized ( this ) { - provided = unscoped.get(); - state.provided.put( key, provided ); + if ( value == null ) + { + value = provider.get(); + } } - - 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 000000000000..099e4dddb8f5 --- /dev/null +++ b/maven-core/src/test/java/org/apache/maven/session/scope/internal/SessionScopeTest.java @@ -0,0 +1,132 @@ +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.locator.DefaultModelLocator; +import org.apache.maven.model.locator.ModelLocator; +import org.apache.maven.plugin.DefaultPluginRealmCache; +import org.apache.maven.plugin.PluginRealmCache; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +public class SessionScopeTest { + + @Test + public void testScope() throws Exception + { + SessionScope scope = new SessionScope(); + + try + { + scope.seed( ModelLocator.class, new DefaultModelLocator() ); + fail( "Expected a " + OutOfScopeException.class.getName() + " exception to be thrown" ); + } + catch ( OutOfScopeException e ) + { + // expected + } + + Provider pml = scope.scope( Key.get( ModelLocator.class), new DefaultModelLocatorProvider() ); + assertNotNull( pml ); + try + { + pml.get(); + fail( "Expected a " + OutOfScopeException.class.getName() + " exception to be thrown" ); + } + catch ( OutOfScopeException e ) + { + // expected + } + + Provider pmst = scope.scope( Key.get( PluginRealmCache.class ), new DefaultPluginRealmCacheProvider() ); + assertNotNull( pmst ); + + scope.enter(); + + final DefaultModelLocator dml1 = new DefaultModelLocator(); + scope.seed( ModelLocator.class, dml1 ); + + assertSame( dml1, pml.get() ); + + PluginRealmCache mst1 = pmst.get(); + assertSame( mst1, pmst.get() ); + Provider pmst1 = scope.scope( Key.get( PluginRealmCache.class ), new DefaultPluginRealmCacheProvider() ); + assertNotNull( pmst1 ); + assertSame( mst1, pmst1.get() ); + + scope.enter(); + + pmst1 = scope.scope( Key.get( PluginRealmCache.class ), new DefaultPluginRealmCacheProvider() ); + assertNotNull( pmst1 ); + assertNotSame( mst1, pmst1.get() ); + + scope.exit(); + + assertSame( mst1, pmst.get() ); + + scope.exit(); + + try + { + pmst.get(); + fail( "Expected a " + OutOfScopeException.class.getName() + " exception to be thrown" ); + } + catch ( OutOfScopeException e ) + { + // expected + } + try + { + scope.seed( ModelLocator.class, new DefaultModelLocator() ); + fail( "Expected a " + OutOfScopeException.class.getName() + " exception to be thrown" ); + } + catch ( OutOfScopeException e ) + { + // expected + } + } + + private static class DefaultPluginRealmCacheProvider implements com.google.inject.Provider + { + @Override + public PluginRealmCache get() + { + return new DefaultPluginRealmCache(); + } + } + + private static class DefaultModelLocatorProvider implements com.google.inject.Provider + { + @Override + public ModelLocator get() + { + return new DefaultModelLocator(); + } + } + +}