This is an automated email from the ASF dual-hosted git repository. jsedding pushed a commit to branch fix/SLING-12019-duplicate-ResourceResolverFactory-registration in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git
commit eb0d3eaded8d9113e4cc15aef3c9b136b6c4b1e9 Author: Julian Sedding <jsedd...@apache.org> AuthorDate: Wed Sep 6 11:59:59 2023 +0200 SLING-12019 - Avoid duplicate ResourceResolverFactory registrations --- .../impl/ResourceResolverFactoryActivator.java | 190 +++++++++++---------- .../stateful/AuthenticatedResourceProvider.java | 9 +- .../impl/MockedResourceResolverImplTest.java | 19 ++- .../impl/mapping/ResourceMapperImplTest.java | 14 +- 4 files changed, 137 insertions(+), 95 deletions(-) diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java index 9e727c7..5e809aa 100644 --- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java +++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverFactoryActivator.java @@ -22,6 +22,11 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import org.apache.commons.collections4.BidiMap; @@ -32,6 +37,7 @@ import org.apache.sling.api.resource.ResourceResolverFactory; import org.apache.sling.api.resource.path.Path; import org.apache.sling.api.resource.runtime.RuntimeService; import org.apache.sling.resourceresolver.impl.helper.ResourceDecoratorTracker; +import org.apache.sling.resourceresolver.impl.mapping.MapEntries; import org.apache.sling.resourceresolver.impl.mapping.Mapping; import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider; import org.apache.sling.resourceresolver.impl.observation.ResourceChangeListenerWhiteboard; @@ -68,17 +74,50 @@ import org.slf4j.LoggerFactory; @Component(name = "org.apache.sling.jcr.resource.internal.JcrResourceResolverFactoryImpl") public class ResourceResolverFactoryActivator { - private static final class FactoryRegistration { /** Registration .*/ - public volatile ServiceRegistration<ResourceResolverFactory> factoryRegistration; + private final ServiceRegistration<ResourceResolverFactory> factoryRegistration; /** Runtime registration. */ - public volatile ServiceRegistration<RuntimeService> runtimeRegistration; + private final ServiceRegistration<RuntimeService> runtimeRegistration; - public volatile CommonResourceResolverFactoryImpl commonFactory; - } + private final CommonResourceResolverFactoryImpl commonFactory; + FactoryRegistration(BundleContext context, ResourceResolverFactoryActivator activator) { + commonFactory = new CommonResourceResolverFactoryImpl(activator); + commonFactory.activate(context); + + // activate and register factory + final Dictionary<String, Object> serviceProps = new Hashtable<>(); + serviceProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation"); + serviceProps.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Resource Resolver Factory"); + factoryRegistration = context.registerService(ResourceResolverFactory.class, + new ServiceFactory<ResourceResolverFactory>() { + + @Override + public ResourceResolverFactory getService(final Bundle bundle, final ServiceRegistration<ResourceResolverFactory> registration) { + if (activator.isDeactivating) { + // TODO - how can this happen? + return null; + } + return new ResourceResolverFactoryImpl(commonFactory, bundle, activator.getServiceUserMapper()); + } + + @Override + public void ungetService(final Bundle bundle, final ServiceRegistration<ResourceResolverFactory> registration, final ResourceResolverFactory service) { + // nothing to do + } + }, serviceProps); + + runtimeRegistration = context.registerService(RuntimeService.class, activator.getRuntimeService(), null); + } + + void unregister() { + runtimeRegistration.unregister(); + factoryRegistration.unregister(); + commonFactory.deactivate(); + } + } /** Logger. */ private final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -145,6 +184,15 @@ public class ResourceResolverFactoryActivator { /** Factory registration. */ private volatile FactoryRegistration factoryRegistration; + /** Queue to serialize registration and unregistration of the ResourceResolverFactory ServiceFactory */ + private final BlockingQueue<Runnable> factoryRegistrationTasks = new LinkedBlockingQueue<>(); + + private ExecutorService factoryRegistrationWorker; + + private static final Runnable POISON_PILL_TASK = () -> {}; + + private volatile boolean isDeactivating = false; + /** * Get the resource decorator tracker. */ @@ -425,20 +473,18 @@ public class ResourceResolverFactoryActivator { @Override public void providerAdded() { - if ( factoryRegistration == null ) { - checkFactoryPreconditions(null, null); - } - + factoryRegistrationTasks.add( + () -> maybeRegisterFactory(null, null)); } @Override public void providerRemoved(final String name, final String pid, final boolean stateful, final boolean isUsed) { - if ( factoryRegistration != null ) { + factoryRegistrationTasks.add(() -> { if ( isUsed && (stateful || config.resource_resolver_providerhandling_paranoid()) ) { unregisterFactory(); } - checkFactoryPreconditions(name, pid); - } + maybeRegisterFactory(name, pid); + }); } }); } else { @@ -446,8 +492,20 @@ public class ResourceResolverFactoryActivator { requiredResourceProvidersLegacy, requiredResourceProviderNames, resourceProviderTracker); - this.checkFactoryPreconditions(null, null); - } + factoryRegistrationTasks.add(() -> maybeRegisterFactory(null, null)); + } + factoryRegistrationWorker = Executors.newSingleThreadExecutor( + r -> new Thread(r, "ResourceResolverFactory registration/deregistration")); + factoryRegistrationWorker.submit(() -> { + try { + Runnable task; + while ((task = factoryRegistrationTasks.take()) != POISON_PILL_TASK) { + task.run(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); } /** @@ -456,6 +514,7 @@ public class ResourceResolverFactoryActivator { @Modified protected void modified(final BundleContext bundleContext, final ResourceResolverFactoryConfig config) { this.deactivate(); + this.isDeactivating = false; this.activate(bundleContext, config); } @@ -464,7 +523,18 @@ public class ResourceResolverFactoryActivator { */ @Deactivate protected void deactivate() { - this.unregisterFactory(); + this.isDeactivating = true; + this.factoryRegistrationTasks.add(this::unregisterFactory); + this.factoryRegistrationTasks.add(POISON_PILL_TASK); + this.factoryRegistrationWorker.shutdown(); + try { + if (!this.factoryRegistrationWorker.awaitTermination(5, TimeUnit.SECONDS)) { + this.factoryRegistrationWorker.shutdownNow(); + this.unregisterFactory(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } this.bundleContext = null; this.config = DEFAULT_CONFIG; @@ -475,11 +545,6 @@ public class ResourceResolverFactoryActivator { this.preconds.deactivate(); this.resourceDecoratorTracker.close(); - - // this is just a sanity call to make sure that unregister - // in the case that a registration happened again - // while deactivation - this.unregisterFactory(); } /** @@ -490,28 +555,13 @@ public class ResourceResolverFactoryActivator { private void unregisterFactory() { FactoryRegistration local = null; synchronized ( this ) { - if ( this.factoryRegistration != null ) { + if (this.factoryRegistration != null) { local = this.factoryRegistration; this.factoryRegistration = null; } } - this.unregisterFactory(local); - } - - /** - * Unregister the provided factory - */ - private void unregisterFactory(final FactoryRegistration local) { - if ( local != null ) { - if ( local.factoryRegistration != null ) { - local.factoryRegistration.unregister(); - } - if ( local.runtimeRegistration != null ) { - local.runtimeRegistration.unregister(); - } - if ( local.commonFactory != null ) { - local.commonFactory.deactivate(); - } + if (local != null) { + local.unregister(); } } @@ -519,40 +569,12 @@ public class ResourceResolverFactoryActivator { * Try to register the factory. */ private void registerFactory(final BundleContext localContext) { - final FactoryRegistration local = new FactoryRegistration(); - - if ( localContext != null ) { - // activate and register factory - final Dictionary<String, Object> serviceProps = new Hashtable<>(); - serviceProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation"); - serviceProps.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Resource Resolver Factory"); - - local.commonFactory = new CommonResourceResolverFactoryImpl(this); - local.commonFactory.activate(localContext); - local.factoryRegistration = localContext.registerService( - ResourceResolverFactory.class, new ServiceFactory<ResourceResolverFactory>() { - - @Override - public ResourceResolverFactory getService(final Bundle bundle, final ServiceRegistration<ResourceResolverFactory> registration) { - if ( ResourceResolverFactoryActivator.this.bundleContext == null ) { - return null; - } - final ResourceResolverFactoryImpl r = new ResourceResolverFactoryImpl( - local.commonFactory, bundle, - ResourceResolverFactoryActivator.this.getServiceUserMapper()); - return r; - } - - @Override - public void ungetService(final Bundle bundle, final ServiceRegistration<ResourceResolverFactory> registration, final ResourceResolverFactory service) { - // nothing to do - } - }, serviceProps); - - local.runtimeRegistration = localContext.registerService(RuntimeService.class, - this.getRuntimeService(), null); - - this.factoryRegistration = local; + if (!this.isDeactivating) { + synchronized (this) { + if (this.factoryRegistration == null) { + this.factoryRegistration = new FactoryRegistration(localContext, this); + } + } } } @@ -575,27 +597,17 @@ public class ResourceResolverFactoryActivator { /** * Check the preconditions and if it changed, either register factory or unregister */ - private void checkFactoryPreconditions(final String unavailableName, final String unavailableServicePid) { - final BundleContext localContext = this.getBundleContext(); - if ( localContext != null ) { - final boolean result = this.preconds.checkPreconditions(unavailableName, unavailableServicePid); - if ( result && this.factoryRegistration == null ) { + private void maybeRegisterFactory(final String unavailableName, final String unavailableServicePid) { + if (!this.isDeactivating) { + final boolean preconditionsOk = this.preconds.checkPreconditions(unavailableName, unavailableServicePid); + if ( preconditionsOk && this.factoryRegistration == null ) { // check system bundle state - if stopping, don't register new factory + final BundleContext localContext = this.getBundleContext(); final Bundle systemBundle = localContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION); if ( systemBundle != null && systemBundle.getState() != Bundle.STOPPING ) { - boolean create = true; - synchronized ( this ) { - if ( this.factoryRegistration == null ) { - this.factoryRegistration = new FactoryRegistration(); - } else { - create = false; - } - } - if ( create ) { - this.registerFactory(localContext); - } + this.registerFactory(localContext); } - } else if ( !result && this.factoryRegistration != null ) { + } else if ( !preconditionsOk && this.factoryRegistration != null ) { this.unregisterFactory(); } } diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java b/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java index 453b5cf..7207b57 100644 --- a/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java +++ b/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java @@ -52,7 +52,7 @@ public class AuthenticatedResourceProvider { private static final Logger logger = LoggerFactory.getLogger(ResourceResolverImpl.class); - public static final AuthenticatedResourceProvider UNAUTHENTICATED_PROVIDER = new AuthenticatedResourceProvider(null, false, null, null); + public static final AuthenticatedResourceProvider UNAUTHENTICATED_PROVIDER = new AuthenticatedResourceProvider(); private final ResourceProviderHandler providerHandler; @@ -79,6 +79,13 @@ public class AuthenticatedResourceProvider { this.useRAS = useRAS; } + private AuthenticatedResourceProvider() { + this.providerHandler = null; + this.resolveContext = null; + this.tracker = null; + this.useRAS = false; + } + /** * Get the resolve context. * @return The resolve context diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java index 7b21023..fa764ae 100644 --- a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java +++ b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -40,6 +41,8 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; @@ -149,13 +152,20 @@ public class MockedResourceResolverImplTest { @SuppressWarnings("unchecked") @Before - public void before() throws LoginException { + public void before() throws LoginException, InterruptedException { activator = new ResourceResolverFactoryActivator(); // system bundle access final Bundle systemBundle = mock(Bundle.class); Mockito.when(systemBundle.getState()).thenReturn(Bundle.ACTIVE); Mockito.when(bundleContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION)).thenReturn(systemBundle); + CountDownLatch factoryRegistrationDone = new CountDownLatch(1); + Mockito.when(bundleContext.registerService(same(ResourceResolverFactory.class), any(ServiceFactory.class), any(Dictionary.class))) + .thenAnswer(invocation -> { + factoryRegistrationDone.countDown(); + return mock(ServiceReference.class); + }); + activator.resourceAccessSecurityTracker = new ResourceAccessSecurityTracker(); activator.resourceProviderTracker = resourceProviderTracker; activator.changeListenerWhiteboard = resourceChangeListenerWhiteboard; @@ -310,14 +320,17 @@ public class MockedResourceResolverImplTest { Mockito.when(usingBundle.getBundleContext()).thenReturn(usingBundleContext); Mockito.when(usingBundleContext.getBundle()).thenReturn(usingBundle); + factoryRegistrationDone.await(5, TimeUnit.SECONDS); + // extract any services that were registered into a map. ArgumentCaptor<Class> classesCaptor = ArgumentCaptor.forClass(Class.class); ArgumentCaptor<ServiceFactory> serviceCaptor = ArgumentCaptor.forClass(ServiceFactory.class); @SuppressWarnings("rawtypes") ArgumentCaptor<Dictionary> propertiesCaptor = ArgumentCaptor.forClass(Dictionary.class); Mockito.verify(bundleContext, Mockito.atLeastOnce()).registerService( - (Class<ResourceResolverFactory>)classesCaptor.capture(), (ServiceFactory<ResourceResolverFactory>)serviceCaptor.capture(), - propertiesCaptor.capture()); + (Class<ResourceResolverFactory>) classesCaptor.capture(), + (ServiceFactory<ResourceResolverFactory>)serviceCaptor.capture(), + propertiesCaptor.capture()); int si = 0; List<ServiceFactory> serviceList = serviceCaptor.getAllValues(); diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java index ddfd024..42bbfe0 100644 --- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java +++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/ResourceMapperImplTest.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; @@ -53,6 +54,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import org.osgi.util.tracker.ServiceTracker; /** * Validates that the {@link ResourceMapperImpl} correctly queries all sources of mappings @@ -94,7 +96,7 @@ public class ResourceMapperImplTest { } @Before - public void prepare() throws LoginException { + public void prepare() throws LoginException, InterruptedException { ctx.registerInjectActivateService(new ServiceUserMapperImpl()); ctx.registerInjectActivateService(new ResourceAccessSecurityTracker()); @@ -139,7 +141,15 @@ public class ResourceMapperImplTest { ctx.registerInjectActivateService(new ResourceResolverFactoryActivator(), "resource.resolver.optimize.alias.resolution", optimiseAliasResolution); - ResourceResolverFactory factory = ctx.getService(ResourceResolverFactory.class); + final ResourceResolverFactory factory; + final ServiceTracker<ResourceResolverFactory, ResourceResolverFactory> tracker = + new ServiceTracker<>(ctx.bundleContext(), ResourceResolverFactory.class, null); + try { + tracker.open(); + factory = tracker.waitForService(TimeUnit.SECONDS.toMillis(5)); + } finally { + tracker.close(); + } assertNotNull(factory);