FELIX-4252 Make Extender's ThreadPool size configurable

* Through "org.apache.felix.ipojo.extender.ThreadPoolSize" System property
* Through ConfigurationAdmin (PID: org.apache.felix.ipojo.extender.ExecutorQueueService)
* Added a ThreadGroup iPOJO Extender's threads

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1527573 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/AbstractService.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/AbstractService.java
index e4f201a..e07455f 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/AbstractService.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/AbstractService.java
@@ -81,4 +81,8 @@
     protected Dictionary<String, ?> getServiceProperties() {
         return null;
     }
+
+    protected ServiceRegistration<?> getRegistration() {
+        return m_registration;
+    }
 }
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/Extender.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/Extender.java
index 4caa387..df03d2e 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/Extender.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/Extender.java
@@ -19,11 +19,15 @@
 
 package org.apache.felix.ipojo.extender.internal;
 
+import java.util.concurrent.ThreadFactory;
+
 import org.apache.felix.ipojo.ConfigurationTracker;
 import org.apache.felix.ipojo.EventDispatcher;
 import org.apache.felix.ipojo.extender.internal.linker.DeclarationLinker;
 import org.apache.felix.ipojo.extender.internal.processor.*;
 import org.apache.felix.ipojo.extender.internal.queue.ExecutorQueueService;
+import org.apache.felix.ipojo.extender.internal.queue.GroupThreadFactory;
+import org.apache.felix.ipojo.extender.internal.queue.NamingThreadFactory;
 import org.apache.felix.ipojo.extender.internal.queue.PrefixedThreadFactory;
 import org.apache.felix.ipojo.extender.internal.queue.SynchronousQueueService;
 import org.apache.felix.ipojo.extender.internal.queue.pref.HeaderPreferenceSelection;
@@ -136,8 +140,17 @@
                     Preference.SYNC,
                     m_logger);
         } else {
+            // Build a thread factory that will groups extender's thread together
+            ThreadFactory threadFactory = new GroupThreadFactory(new ThreadGroup("iPOJO Extender"));
+            threadFactory = new NamingThreadFactory(threadFactory);
+            threadFactory = new PrefixedThreadFactory(threadFactory, "[iPOJO] ");
+
+            // Create the queue services
             SynchronousQueueService sync = new SynchronousQueueService(context);
-            ExecutorQueueService async = new ExecutorQueueService(context, 1, new PrefixedThreadFactory("[iPOJO] "));
+            ExecutorQueueService async = new ExecutorQueueService(context,
+                                                                  Integer.getInteger(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY,
+                                                                                     1), // default to 1 if no system property is set
+                                                                  threadFactory);
             m_queueService = new PreferenceQueueService(new HeaderPreferenceSelection(), sync, async);
 
             extensionBundleProcessor = new QueuingActivationProcessor(extensionBundleProcessor, m_queueService);
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueService.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueService.java
index 9e801fb..6cbb2c8 100644
--- a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueService.java
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueService.java
@@ -25,6 +25,9 @@
 import org.apache.felix.ipojo.extender.queue.JobInfo;
 import org.apache.felix.ipojo.extender.queue.QueueService;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
 
 import java.util.*;
 import java.util.concurrent.*;
@@ -32,7 +35,17 @@
 /**
  * An asynchronous implementation of the queue service. This implementation relies on an executor service.
  */
-public class ExecutorQueueService extends AbstractService implements LifecycleQueueService {
+public class ExecutorQueueService extends AbstractService implements LifecycleQueueService, ManagedService {
+
+    /**
+     * Property name used to configure this ThreadPool's size (usable as System Property or ConfigAdmin property).
+     */
+    public static final String THREADPOOL_SIZE_PROPERTY = "org.apache.felix.ipojo.extender.ThreadPoolSize";
+
+    /**
+     * Service PID used to identify service with ConfigAdmin.
+     */
+    public static final String EXECUTOR_QUEUE_SERVICE_PID = "org.apache.felix.ipojo.extender.ExecutorQueueService";
 
     /**
      * The default thread pool size (3).
@@ -42,7 +55,7 @@
     /**
      * The executor service.
      */
-    private final ExecutorService m_executorService;
+    private final ThreadPoolExecutor m_executorService;
 
     /**
      * The statistics populated by this queue service.
@@ -50,6 +63,16 @@
     private final Statistic m_statistic = new Statistic();
 
     /**
+     * Store service properties (used when updating their values)
+     */
+    private Hashtable<String, Object> m_properties;
+
+    /**
+     * Initial thread pool size.
+     */
+    private final int initialSize;
+
+    /**
      * Creates the queue service using the default pool size.
      *
      * @param bundleContext the bundle context.
@@ -65,7 +88,7 @@
      * @param size          the thread pool size.
      */
     public ExecutorQueueService(BundleContext bundleContext, int size) {
-        this(bundleContext, Executors.newFixedThreadPool(size));
+        this(bundleContext, (ThreadPoolExecutor) Executors.newFixedThreadPool(size));
     }
 
     /**
@@ -76,7 +99,7 @@
      * @param threadFactory the thread factory
      */
     public ExecutorQueueService(BundleContext bundleContext, int size, ThreadFactory threadFactory) {
-        this(bundleContext, Executors.newFixedThreadPool(size, threadFactory));
+        this(bundleContext, (ThreadPoolExecutor) Executors.newFixedThreadPool(size, threadFactory));
     }
 
 
@@ -87,9 +110,11 @@
      * @param bundleContext   the bundle context
      * @param executorService the executor service we have to use
      */
-    private ExecutorQueueService(BundleContext bundleContext, ExecutorService executorService) {
+    private ExecutorQueueService(BundleContext bundleContext, ThreadPoolExecutor executorService) {
         super(bundleContext, QueueService.class);
         m_executorService = executorService;
+        initialSize = executorService.getCorePoolSize();
+        m_properties = getDefaultProperties();
     }
 
     /**
@@ -108,9 +133,15 @@
 
     @Override
     protected Dictionary<String, ?> getServiceProperties() {
-        Hashtable<String, Object> properties = new Hashtable<String, Object>();
-        properties.put(QueueService.QUEUE_MODE_PROPERTY, QueueService.ASYNCHRONOUS_QUEUE_MODE);
-        return properties;
+        return m_properties;
+    }
+
+    private Hashtable<String, Object> getDefaultProperties() {
+        Hashtable<String, Object> initial = new Hashtable<String, Object>();
+        initial.put(Constants.SERVICE_PID, EXECUTOR_QUEUE_SERVICE_PID);
+        initial.put(QueueService.QUEUE_MODE_PROPERTY, QueueService.ASYNCHRONOUS_QUEUE_MODE);
+        initial.put(THREADPOOL_SIZE_PROPERTY, initialSize);
+        return initial;
     }
 
     public int getFinished() {
@@ -154,4 +185,56 @@
         return submit(callable, "No description");
     }
 
+    public void updated(Dictionary properties) throws ConfigurationException {
+
+        // Default configuration
+        if (properties == null) {
+            properties = getDefaultProperties();
+        }
+
+        boolean changed = false;
+
+        // Try to read configuration
+        Object o = properties.get(THREADPOOL_SIZE_PROPERTY);
+        if (o != null) {
+            // Convert value
+            Integer newSize = getIntegerProperty(o, DEFAULT_QUEUE_SIZE);
+
+            if (newSize != m_executorService.getMaximumPoolSize()) {
+                // Apply configuration change
+                m_executorService.setCorePoolSize(newSize);
+                m_executorService.setMaximumPoolSize(newSize);
+                m_properties.put(THREADPOOL_SIZE_PROPERTY, newSize);
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            // Transfer unrecognized values in service properties as per spec. recommendation
+            for (Object key : Collections.list(properties.keys())) {
+                if (!THREADPOOL_SIZE_PROPERTY.equals(key)) {
+                    m_properties.put(key.toString(), properties.get(key));
+                }
+            }
+
+            // Update registration object
+            getRegistration().setProperties(m_properties);
+        }
+
+    }
+
+    private Integer getIntegerProperty(final Object value, final Integer defaultValue) throws ConfigurationException {
+        Integer newSize = null;
+        if (value instanceof Integer) {
+            newSize = (Integer) value;
+        } else {
+            // Try to convert the value
+            try {
+                newSize = Integer.parseInt(value.toString());
+            } catch (NumberFormatException e) {
+                return defaultValue;
+            }
+        }
+        return newSize;
+    }
 }
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/GroupThreadFactory.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/GroupThreadFactory.java
new file mode 100644
index 0000000..b1b5b5c
--- /dev/null
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/GroupThreadFactory.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package org.apache.felix.ipojo.extender.internal.queue;
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * A thread factory that groups produced threads inside a given {@link java.lang.ThreadGroup}.
+ */
+public class GroupThreadFactory implements ThreadFactory {
+
+    /**
+     * Group for produced Threads.
+     */
+    private final ThreadGroup m_threadGroup;
+
+    public GroupThreadFactory() {
+        this(defaultThreadGroup());
+    }
+
+    /**
+     * Returns the default thread group just like {@link java.util.concurrent.Executors#defaultThreadFactory()}.
+     */
+    private static ThreadGroup defaultThreadGroup() {
+        SecurityManager s = System.getSecurityManager();
+        if (s != null) {
+            return s.getThreadGroup();
+        } else {
+            return Thread.currentThread().getThreadGroup();
+        }
+    }
+
+    /**
+     * @param threadGroup group to be used for produced threads.
+     */
+    public GroupThreadFactory(final ThreadGroup threadGroup) {
+        m_threadGroup = threadGroup;
+    }
+
+    /**
+     * Creates a new thread.
+     * Prepend the prefix to the thread name
+     *
+     * @param r the runnable
+     * @return the thread object
+     */
+    public Thread newThread(Runnable r) {
+        return new Thread(m_threadGroup, r);
+    }
+}
diff --git a/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/NamingThreadFactory.java b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/NamingThreadFactory.java
new file mode 100644
index 0000000..82f8540
--- /dev/null
+++ b/ipojo/runtime/core/src/main/java/org/apache/felix/ipojo/extender/internal/queue/NamingThreadFactory.java
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+package org.apache.felix.ipojo.extender.internal.queue;
+
+import static java.lang.String.format;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A thread factory setting the name of the created thread.
+ * This thread factory delegates the thread creation on another factory and format the name.
+ */
+public class NamingThreadFactory implements ThreadFactory {
+
+    /**
+     * Unique identifier generator.
+     */
+    private static final AtomicInteger IDENTIFIERS = new AtomicInteger(1);
+
+    /**
+     * Per-factory counter
+     */
+    private final AtomicInteger threadNumber = new AtomicInteger(1);
+
+    /**
+     * The wrapped thread factory on which creation is delegated.
+     */
+    private final ThreadFactory m_threadFactory;
+
+    /**
+     * Pool identifier.
+     */
+    private final int m_identifier;
+
+    /**
+     * For test only.
+     */
+    public static void reset() {
+        IDENTIFIERS.set(1);
+    }
+
+    /**
+     * Creates the object delegating to the given thread factory.
+     *
+     * @param threadFactory the thread factory
+     */
+    public NamingThreadFactory(ThreadFactory threadFactory) {
+        this(threadFactory, IDENTIFIERS.getAndIncrement());
+    }
+
+    /**
+     * Creates the object delegating to the given thread factory.
+     *
+     * @param threadFactory the thread factory
+     * @param identifier the pool identifier
+     */
+    private NamingThreadFactory(final ThreadFactory threadFactory, final int identifier) {
+        m_threadFactory = threadFactory;
+        m_identifier = identifier;
+    }
+
+    /**
+     * Creates a new thread.
+     * Format the Thread name
+     *
+     * @param r the runnable
+     * @return the thread object
+     */
+    public Thread newThread(Runnable r) {
+        Thread thread = m_threadFactory.newThread(r);
+        thread.setName(format("pool-%d-thread-%d", m_identifier, threadNumber.getAndIncrement()));
+        return thread;
+    }
+}
diff --git a/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueServiceTestCase.java b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueServiceTestCase.java
index 363eaab..6699cd4 100644
--- a/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueServiceTestCase.java
+++ b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/ExecutorQueueServiceTestCase.java
@@ -22,9 +22,12 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import java.util.Dictionary;
+import java.util.Hashtable;
 import java.util.concurrent.Future;
 
 import org.apache.felix.ipojo.extender.internal.queue.callable.SleepingCallable;
@@ -32,6 +35,8 @@
 import org.apache.felix.ipojo.extender.queue.Callback;
 import org.apache.felix.ipojo.extender.queue.JobInfo;
 import org.apache.felix.ipojo.extender.queue.QueueService;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -54,6 +59,9 @@
     @Mock
     private Callback<String> m_callback;
 
+    @Captor
+    private ArgumentCaptor<Dictionary<String, ?>> m_captor;
+
     @Override
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -114,6 +122,73 @@
         queueService.stop();
     }
 
+    public void testConfigurationUpdate() throws Exception {
+        ExecutorQueueService queueService = new ExecutorQueueService(m_bundleContext, 1);
+
+        Mockito.<ServiceRegistration<?>>when(m_bundleContext.registerService(eq(QueueService.class.getName()),
+                                                                             eq(queueService),
+                                                                             any(Dictionary.class)))
+               .thenReturn(m_registration);
+
+        queueService.start();
+
+        verify(m_bundleContext).registerService(eq(QueueService.class.getName()), eq(queueService), m_captor.capture());
+
+        // Verify initial value is 1
+        Dictionary<String, ?> initial = m_captor.getValue();
+        assertEquals(1, initial.get(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY));
+
+        Dictionary<String, Object> update = new Hashtable<String, Object>();
+        update.put(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY, 3);
+        queueService.updated(update);
+
+        verify(m_registration).setProperties(m_captor.capture());
+        assertEquals(3, m_captor.getValue().get(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY));
+    }
+
+    public void testManagedServiceUpdatedWithNull() throws Exception {
+        ExecutorQueueService queueService = new ExecutorQueueService(m_bundleContext, 1);
+
+        Mockito.<ServiceRegistration<?>>when(m_bundleContext.registerService(eq(QueueService.class.getName()),
+                                                                             eq(queueService),
+                                                                             any(Dictionary.class)))
+               .thenReturn(m_registration);
+
+        queueService.start();
+
+        verify(m_bundleContext).registerService(eq(QueueService.class.getName()), eq(queueService), m_captor.capture());
+
+        // Verify initial value is 1
+        Dictionary<String, ?> initial = m_captor.getValue();
+        assertEquals(1, initial.get(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY));
+
+        // Change the value once and then apply a null configuration
+        // Service should use it's default configuration
+        Dictionary<String, Object> update = new Hashtable<String, Object>();
+        update.put(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY, 3);
+        queueService.updated(update);
+        queueService.updated(null);
+
+        verify(m_registration, times(2)).setProperties(m_captor.capture());
+        assertEquals(1, m_captor.getValue().get(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY));
+    }
+
+    public void testConfigurationUpdatedWithNoChanges() throws Exception {
+        ExecutorQueueService queueService = new ExecutorQueueService(m_bundleContext, 1);
+
+        Mockito.<ServiceRegistration<?>>when(m_bundleContext.registerService(eq(QueueService.class.getName()),
+                                                                             eq(queueService),
+                                                                             any(Dictionary.class)))
+               .thenReturn(m_registration);
+
+        queueService.start();
+
+        Dictionary<String, Object> update = new Hashtable<String, Object>();
+        update.put(ExecutorQueueService.THREADPOOL_SIZE_PROPERTY, 1);
+        queueService.updated(update);
+
+        verifyZeroInteractions(m_registration);
+    }
 }
 
 
diff --git a/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/GroupThreadFactoryTestCase.java b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/GroupThreadFactoryTestCase.java
new file mode 100644
index 0000000..93834bc
--- /dev/null
+++ b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/GroupThreadFactoryTestCase.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+package org.apache.felix.ipojo.extender.internal.queue;
+
+import junit.framework.TestCase;
+
+/**
+ * User: guillaume
+ * Date: 30/09/13
+ * Time: 15:37
+ */
+public class GroupThreadFactoryTestCase extends TestCase {
+
+    public void testThreadsAreInTheSpecifiedGroup() throws Exception {
+        ThreadGroup group = new ThreadGroup("Test");
+        GroupThreadFactory factory = new GroupThreadFactory(group);
+
+        Thread thread = factory.newThread(new EmptyRunnable());
+
+        assertEquals(group, thread.getThreadGroup());
+    }
+
+    private static class EmptyRunnable implements Runnable {
+        public void run() {
+            // do nothing
+        }
+    }
+}
diff --git a/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/NamingThreadFactoryTestCase.java b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/NamingThreadFactoryTestCase.java
new file mode 100644
index 0000000..c9bd2bd
--- /dev/null
+++ b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/NamingThreadFactoryTestCase.java
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+package org.apache.felix.ipojo.extender.internal.queue;
+
+import java.util.concurrent.ThreadFactory;
+
+import junit.framework.TestCase;
+
+/**
+ * User: guillaume
+ * Date: 30/09/13
+ * Time: 15:46
+ */
+public class NamingThreadFactoryTestCase extends TestCase {
+
+    @Override
+    public void setUp() throws Exception {
+        // Ensure pool numbering starts at 1 for each test
+        NamingThreadFactory.reset();
+    }
+
+    public void testSequentialThreadNaming() throws Exception {
+        NamingThreadFactory factory = new NamingThreadFactory(new DefaultThreadFactory());
+
+        Thread t1 = factory.newThread(new EmptyRunnable());
+        Thread t2 = factory.newThread(new EmptyRunnable());
+        Thread t3 = factory.newThread(new EmptyRunnable());
+        Thread t4 = factory.newThread(new EmptyRunnable());
+
+        assertEquals("pool-1-thread-1", t1.getName());
+        assertEquals("pool-1-thread-2", t2.getName());
+        assertEquals("pool-1-thread-3", t3.getName());
+        assertEquals("pool-1-thread-4", t4.getName());
+    }
+
+    public void testMultiThreadNamingFactories() throws Exception {
+        NamingThreadFactory factory1 = new NamingThreadFactory(new DefaultThreadFactory());
+        NamingThreadFactory factory2 = new NamingThreadFactory(new DefaultThreadFactory());
+
+        // Interleaved invocations
+        Thread t21 = factory2.newThread(new EmptyRunnable());
+        Thread t11 = factory1.newThread(new EmptyRunnable());
+        Thread t12 = factory1.newThread(new EmptyRunnable());
+        Thread t13 = factory1.newThread(new EmptyRunnable());
+        Thread t22 = factory2.newThread(new EmptyRunnable());
+        Thread t23 = factory2.newThread(new EmptyRunnable());
+        Thread t14 = factory1.newThread(new EmptyRunnable());
+        Thread t24 = factory2.newThread(new EmptyRunnable());
+
+        assertEquals("pool-1-thread-1", t11.getName());
+        assertEquals("pool-1-thread-2", t12.getName());
+        assertEquals("pool-1-thread-3", t13.getName());
+        assertEquals("pool-1-thread-4", t14.getName());
+
+        assertEquals("pool-2-thread-1", t21.getName());
+        assertEquals("pool-2-thread-2", t22.getName());
+        assertEquals("pool-2-thread-3", t23.getName());
+        assertEquals("pool-2-thread-4", t24.getName());
+    }
+
+    private static class DefaultThreadFactory implements ThreadFactory {
+        public Thread newThread(final Runnable r) {
+            return new Thread(r);
+        }
+    }
+
+    private static class EmptyRunnable implements Runnable {
+        public void run() {
+            // do nothing
+        }
+    }
+}
diff --git a/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/PrefixedThreadFactoryTestCase.java b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/PrefixedThreadFactoryTestCase.java
index ec00530..e101ee1 100644
--- a/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/PrefixedThreadFactoryTestCase.java
+++ b/ipojo/runtime/core/src/test/java/org/apache/felix/ipojo/extender/internal/queue/PrefixedThreadFactoryTestCase.java
@@ -26,8 +26,6 @@
 
 import java.util.concurrent.ThreadFactory;
 
-import org.apache.felix.ipojo.extender.internal.queue.PrefixedThreadFactory;
-
 import junit.framework.TestCase;
 
 /**