SLING-2558 : Potential Deadlocks may be caused by AdapterManager
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1372417 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptor.java b/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptor.java
index fe64e9c..efb215a 100644
--- a/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptor.java
+++ b/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptor.java
@@ -29,7 +29,7 @@
*/
public class AdapterFactoryDescriptor {
- private AdapterFactory factory;
+ private volatile AdapterFactory factory;
private final String[] adapters;
@@ -48,12 +48,8 @@
public AdapterFactory getFactory() {
if ( factory == null ) {
- synchronized ( this ) {
- if ( factory == null ) {
- factory = (AdapterFactory) context.locateService(
- "AdapterFactory", reference);
- }
- }
+ factory = (AdapterFactory) context.locateService(
+ "AdapterFactory", reference);
}
return factory;
}
diff --git a/src/main/java/org/apache/sling/adapter/internal/AdapterManagerImpl.java b/src/main/java/org/apache/sling/adapter/internal/AdapterManagerImpl.java
index a0f2563..27ce36e 100644
--- a/src/main/java/org/apache/sling/adapter/internal/AdapterManagerImpl.java
+++ b/src/main/java/org/apache/sling/adapter/internal/AdapterManagerImpl.java
@@ -24,10 +24,14 @@
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
@@ -89,18 +93,19 @@
*
* @see AdapterFactoryDescriptorMap
*/
- private Map<String, AdapterFactoryDescriptorMap> factories = new HashMap<String, AdapterFactoryDescriptorMap>();
+ private Map<String, AdapterFactoryDescriptorMap> descriptors = new HashMap<String, AdapterFactoryDescriptorMap>();
/**
- * Matrix of {@link AdapterFactory} instances primarily indexed by the fully
+ * Matrix of {@link AdapterFactoryDescriptor} instances primarily indexed by the fully
* qualified name of the class to be adapted and secondarily indexed by the
* fully qualified name of the class to adapt to (the target class).
* <p>
* This cache is built on demand by calling the
- * {@link #getAdapterFactories(Class)} class. It is removed altogether
+ * {@link #getAdapterFactories(Class)} method. It is cleared
* whenever an adapter factory is registered on unregistered.
*/
- private Map<String, Map<String, AdapterFactory>> factoryCache;
+ private final ConcurrentMap<String, Map<String, AdapterFactoryDescriptor>> factoryCache
+ = new ConcurrentHashMap<String, Map<String, AdapterFactoryDescriptor>>();
/**
* The service tracker for the event admin
@@ -113,15 +118,18 @@
/**
* Returns the adapted <code>adaptable</code> or <code>null</code> if
* the object cannot be adapted.
+ *
+ * @see org.apache.sling.api.adapter.AdapterManager#getAdapter(java.lang.Object, java.lang.Class)
*/
- public <AdapterType> AdapterType getAdapter(Object adaptable,
- Class<AdapterType> type) {
+ public <AdapterType> AdapterType getAdapter(final Object adaptable,
+ final Class<AdapterType> type) {
// get the adapter factories for the type of adaptable object
- Map<String, AdapterFactory> factories = getAdapterFactories(adaptable.getClass());
+ final Map<String, AdapterFactoryDescriptor> factories = getAdapterFactories(adaptable.getClass());
// get the factory for the target type
- AdapterFactory factory = factories.get(type.getName());
+ final AdapterFactoryDescriptor desc = factories.get(type.getName());
+ final AdapterFactory factory = desc == null ? null : desc.getFactory();
// have the factory adapt the adaptable if the factory exists
if (factory != null) {
@@ -139,7 +147,12 @@
// ----------- SCR integration ---------------------------------------------
- protected void activate(ComponentContext context) {
+ /**
+ * Activate the manager.
+ * Bind all already registered factories
+ * @param context Component context
+ */
+ protected void activate(final ComponentContext context) {
this.context = context;
// register all adapter factories bound before activation
@@ -148,7 +161,7 @@
refs = new ArrayList<ServiceReference>(this.boundAdapterFactories);
boundAdapterFactories.clear();
}
- for (ServiceReference reference : refs) {
+ for (final ServiceReference reference : refs) {
registerAdapterFactory(context, reference);
}
@@ -157,14 +170,18 @@
}
/**
+ * Deactivate
* @param context Not used
*/
- protected void deactivate(ComponentContext context) {
+ protected void deactivate(final ComponentContext context) {
SyntheticResource.unsetAdapterManager(this);
this.context = null;
}
- protected void bindAdapterFactory(ServiceReference reference) {
+ /**
+ * Bind a new adapter factory.
+ */
+ protected void bindAdapterFactory(final ServiceReference reference) {
boolean create = true;
if (context == null) {
synchronized ( this.boundAdapterFactories ) {
@@ -179,7 +196,10 @@
}
}
- protected void unbindAdapterFactory(ServiceReference reference) {
+ /**
+ * Unbind a adapter factory.
+ */
+ protected void unbindAdapterFactory(final ServiceReference reference) {
unregisterAdapterFactory(reference);
}
@@ -192,7 +212,7 @@
* MODIFIED WITHOUT NOTICE.</em></strong>
*/
Map<String, AdapterFactoryDescriptorMap> getFactories() {
- return factories;
+ return descriptors;
}
/**
@@ -201,7 +221,7 @@
* <strong><em>THIS METHOD IS FOR UNIT TESTING ONLY. IT MAY BE REMOVED OR
* MODIFIED WITHOUT NOTICE.</em></strong>
*/
- Map<String, Map<String, AdapterFactory>> getFactoryCache() {
+ Map<String, Map<String, AdapterFactoryDescriptor>> getFactoryCache() {
return factoryCache;
}
@@ -209,8 +229,8 @@
* Unregisters the {@link AdapterFactory} referred to by the service
* <code>reference</code> from the registry.
*/
- private void registerAdapterFactory(ComponentContext context,
- ServiceReference reference) {
+ private void registerAdapterFactory(final ComponentContext context,
+ final ServiceReference reference) {
final String[] adaptables = PropertiesUtil.toStringArray(reference.getProperty(ADAPTABLE_CLASSES));
final String[] adapters = PropertiesUtil.toStringArray(reference.getProperty(ADAPTER_CLASSES));
@@ -222,19 +242,22 @@
final AdapterFactoryDescriptor factoryDesc = new AdapterFactoryDescriptor(context,
reference, adapters);
- synchronized (factories) {
- for (final String adaptable : adaptables) {
- AdapterFactoryDescriptorMap adfMap = factories.get(adaptable);
+ for (final String adaptable : adaptables) {
+ AdapterFactoryDescriptorMap adfMap = null;
+ synchronized ( this.descriptors ) {
+ adfMap = descriptors.get(adaptable);
if (adfMap == null) {
adfMap = new AdapterFactoryDescriptorMap();
- factories.put(adaptable, adfMap);
+ descriptors.put(adaptable, adfMap);
}
+ }
+ synchronized ( adfMap ) {
adfMap.put(reference, factoryDesc);
}
}
// clear the factory cache to force rebuild on next access
- factoryCache = null;
+ this.factoryCache.clear();
// send event
final EventAdmin localEA = this.eventAdmin;
@@ -251,7 +274,7 @@
* Unregisters the {@link AdapterFactory} referred to by the service
* <code>reference</code> from the registry.
*/
- private void unregisterAdapterFactory(ServiceReference reference) {
+ private void unregisterAdapterFactory(final ServiceReference reference) {
synchronized ( this.boundAdapterFactories ) {
boundAdapterFactories.remove(reference);
}
@@ -264,14 +287,14 @@
}
boolean factoriesModified = false;
- synchronized (factories) {
- for (String adaptable : adaptables) {
- AdapterFactoryDescriptorMap adfMap = factories.get(adaptable);
- if (adfMap != null) {
+ AdapterFactoryDescriptorMap adfMap = null;
+ for (final String adaptable : adaptables) {
+ synchronized ( this.descriptors ) {
+ adfMap = this.descriptors.get(adaptable);
+ }
+ if (adfMap != null) {
+ synchronized ( adfMap ) {
factoriesModified |= (adfMap.remove(reference) != null);
- if (adfMap.isEmpty()) {
- factories.remove(adaptable);
- }
}
}
}
@@ -279,7 +302,7 @@
// only remove cache if some adapter factories have actually been
// removed
if (factoriesModified) {
- factoryCache = null;
+ this.factoryCache.clear();
}
// send event
@@ -294,28 +317,6 @@
}
/**
- * Returns a map of {@link AdapterFactory} instances for the given class to
- * be adapted. The returned map is indexed by the fully qualified name of
- * the target classes (to adapt to) registered.
- *
- * @param clazz The type of the object for which the registered adapter
- * factories are requested
- * @return The map of adapter factories. If there is no adapter factory
- * registered for this type, the returned map is empty.
- */
- private Map<String, AdapterFactory> getAdapterFactories(Class<?> clazz) {
- Map<String, Map<String, AdapterFactory>> cache = factoryCache;
- if (cache == null) {
- cache = new HashMap<String, Map<String, AdapterFactory>>();
- factoryCache = cache;
- }
-
- synchronized (cache) {
- return getAdapterFactories(clazz, cache);
- }
- }
-
- /**
* Returns the map of adapter factories index by adapter (target) class name
* for the given adaptable <code>clazz</code>. If no adapter exists for
* the <code>clazz</code> and empty map is returned.
@@ -327,15 +328,13 @@
* empty if there is no adapter factory for the adaptable
* <code>clazz</code>.
*/
- private Map<String, AdapterFactory> getAdapterFactories(Class<?> clazz,
- Map<String, Map<String, AdapterFactory>> cache) {
-
- String className = clazz.getName();
- Map<String, AdapterFactory> entry = cache.get(className);
+ private Map<String, AdapterFactoryDescriptor> getAdapterFactories(final Class<?> clazz) {
+ final String className = clazz.getName();
+ Map<String, AdapterFactoryDescriptor> entry = this.factoryCache.get(className);
if (entry == null) {
// create entry
- entry = createAdapterFactoryMap(clazz, cache);
- cache.put(className, entry);
+ entry = createAdapterFactoryMap(clazz);
+ this.factoryCache.put(className, entry);
}
return entry;
@@ -350,43 +349,45 @@
*
* @param clazz The adaptable <code>Class</code> for which to build the
* adapter factory map by target class name.
- * @param cache The cache of already defined adapter factory mappings
* @return The map of adapter factories by target class name. The map may be
* empty if there is no adapter factory for the adaptable
* <code>clazz</code>.
*/
- private Map<String, AdapterFactory> createAdapterFactoryMap(Class<?> clazz,
- Map<String, Map<String, AdapterFactory>> cache) {
- Map<String, AdapterFactory> afm = new HashMap<String, AdapterFactory>();
+ private Map<String, AdapterFactoryDescriptor> createAdapterFactoryMap(final Class<?> clazz) {
+ final Map<String, AdapterFactoryDescriptor> afm = new HashMap<String, AdapterFactoryDescriptor>();
// AdapterFactories for this class
- synchronized (factories) {
- AdapterFactoryDescriptorMap afdMap = factories.get(clazz.getName());
- if (afdMap != null) {
- for (AdapterFactoryDescriptor afd : afdMap.values()) {
- String[] adapters = afd.getAdapters();
- for (String adapter : adapters) {
- if (!afm.containsKey(adapter)) {
- final AdapterFactory factory = afd.getFactory();
- if (factory != null) {
- afm.put(adapter, factory);
- }
- }
+ AdapterFactoryDescriptorMap afdMap = null;
+ synchronized ( this.descriptors ) {
+ afdMap = this.descriptors.get(clazz.getName());
+ }
+ if (afdMap != null) {
+ final Set<AdapterFactoryDescriptor> afdSet;
+ synchronized ( afdMap ) {
+ afdSet = new HashSet<AdapterFactoryDescriptor>(afdMap.values());
+ }
+ for (final AdapterFactoryDescriptor afd : afdSet) {
+ final String[] adapters = afd.getAdapters();
+ for (final String adapter : adapters) {
+ // to handle service ranking, we only add if the map does not
+ // have a value for this adapter yet
+ if (!afm.containsKey(adapter)) {
+ afm.put(adapter, afd);
}
}
}
}
// AdapterFactories for the interfaces
- Class<?>[] interfaces = clazz.getInterfaces();
- for (Class<?> iFace : interfaces) {
- copyAdapterFactories(afm, iFace, cache);
+ final Class<?>[] interfaces = clazz.getInterfaces();
+ for (final Class<?> iFace : interfaces) {
+ copyAdapterFactories(afm, iFace);
}
// AdapterFactories for the super class
- Class<?> superClazz = clazz.getSuperclass();
+ final Class<?> superClazz = clazz.getSuperclass();
if (superClazz != null) {
- copyAdapterFactories(afm, superClazz, cache);
+ copyAdapterFactories(afm, superClazz);
}
return afm;
@@ -403,19 +404,16 @@
* replaced.
* @param clazz The adaptable class whose adapter factories are considered
* for adding into <code>dest</code>.
- * @param cache The adapter factory cache providing the adapter factories
- * for <code>clazz</code> to consider for copying into
- * <code>dest</code>.
*/
- private void copyAdapterFactories(Map<String, AdapterFactory> dest,
- Class<?> clazz, Map<String, Map<String, AdapterFactory>> cache) {
+ private void copyAdapterFactories(final Map<String, AdapterFactoryDescriptor> dest,
+ final Class<?> clazz) {
// get the adapter factories for the adaptable clazz
- Map<String, AdapterFactory> scMap = getAdapterFactories(clazz, cache);
+ final Map<String, AdapterFactoryDescriptor> scMap = getAdapterFactories(clazz);
// for each target class copy the entry to dest if dest does
// not contain the target class already
- for (Map.Entry<String, AdapterFactory> entry : scMap.entrySet()) {
+ for (Map.Entry<String, AdapterFactoryDescriptor> entry : scMap.entrySet()) {
if (!dest.containsKey(entry.getKey())) {
dest.put(entry.getKey(), entry.getValue());
}
diff --git a/src/test/java/org/apache/sling/adapter/internal/AdapterManagerTest.java b/src/test/java/org/apache/sling/adapter/internal/AdapterManagerTest.java
index e94a26b..d6fa2e7 100644
--- a/src/test/java/org/apache/sling/adapter/internal/AdapterManagerTest.java
+++ b/src/test/java/org/apache/sling/adapter/internal/AdapterManagerTest.java
@@ -161,7 +161,7 @@
@org.junit.Test public void testUnitialized() {
assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
- assertNull("AdapterFactory cache must be null", am.getFactoryCache());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
}
@org.junit.Test public void testInitialized() throws Exception {
@@ -169,7 +169,7 @@
assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
- assertNull("AdapterFactory cache must be null", am.getFactoryCache());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
}
@org.junit.Test public void testBindBeforeActivate() throws Exception {
@@ -179,14 +179,14 @@
// no cache and no factories yet
assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
- assertNull("AdapterFactory cache must be null", am.getFactoryCache());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
am.activate(this.createComponentContext());
// expect the factory, but cache is empty
assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
assertEquals("AdapterFactoryDescriptors must contain one entry", 1, am.getFactories().size());
- assertNull("AdapterFactory cache must be null", am.getFactoryCache());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
}
@org.junit.Test public void testBindAfterActivate() throws Exception {
@@ -195,7 +195,7 @@
// no cache and no factories yet
assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
- assertNull("AdapterFactory cache must be null", am.getFactoryCache());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
final ServiceReference ref = createServiceReference();
am.bindAdapterFactory(ref);
@@ -203,7 +203,7 @@
// expect the factory, but cache is empty
assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
assertEquals("AdapterFactoryDescriptors must contain one entry", 1, am.getFactories().size());
- assertNull("AdapterFactory cache must be null", am.getFactoryCache());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
Map<String, AdapterFactoryDescriptorMap> f = am.getFactories();
AdapterFactoryDescriptorMap afdm = f.get(TestSlingAdaptable.class.getName());