SLING-5831 : Support different thread pools for scheduled tasks

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1752822 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 6f399f4..a94606b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -160,7 +160,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.threads</artifactId>
-            <version>3.0.0</version>
+            <version>3.2.6</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/commons/scheduler/ScheduleOptions.java b/src/main/java/org/apache/sling/commons/scheduler/ScheduleOptions.java
index 7395859..0b5e6f1 100644
--- a/src/main/java/org/apache/sling/commons/scheduler/ScheduleOptions.java
+++ b/src/main/java/org/apache/sling/commons/scheduler/ScheduleOptions.java
@@ -91,6 +91,7 @@
      *
      * If {@link #onLeaderOnly(boolean)} or {@link #onSingleInstanceOnly(boolean)} has been called before,
      * that option is reset and overwritten by the value of this method.
+     *
      * @param slingIds Array of Sling IDs this job should run on
      * @return The schedule options.
      */
@@ -99,12 +100,13 @@
     /**
      * Define the thread pool to be used.
      * Scheduled jobs can run using different thread pools. By default, the default
-     * thread pool from the thread pool manager service is used.
-     * If a thread pool name is specified, a pool with that name will be get from
-     * the thread pool manager. If such a pool does not exist, it will be created.
+     * thread pool of the scheduler is used.
+     * If a thread pool name is specified, it is up to the scheduler to put the job
+     * in the defined thread pool or any other thread pool.
      * This option must be used with special care as it might create new thread pools.
      * It should only be used if there is a good reason to not use the default thread
      * pool.
+     *
      * @param name The thread pool name
      * @return The schedule options.
      * @since 2.5.0
diff --git a/src/main/java/org/apache/sling/commons/scheduler/Scheduler.java b/src/main/java/org/apache/sling/commons/scheduler/Scheduler.java
index 83af956..cf87f84 100644
--- a/src/main/java/org/apache/sling/commons/scheduler/Scheduler.java
+++ b/src/main/java/org/apache/sling/commons/scheduler/Scheduler.java
@@ -104,9 +104,9 @@
     /**
      * Name of the configuration property to define the thread pool to be used.
      * Scheduled jobs can run using different thread pools. By default, the default
-     * thread pool from the thread pool manager service is used.
-     * If a thread pool name is specified, a pool with that name will be get from
-     * the thread pool manager. If such a pool does not exist, it will be created.
+     * thread pool of the scheduler is used.
+     * If a thread pool name is specified, it is up to the scheduler to put the job
+     * in the defined thread pool or any other thread pool.
      * This option must be used with special care as it might create new thread pools.
      * It should only be used if there is a good reason to not use the default thread
      * pool.
diff --git a/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java b/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java
index 20aa9a5..070ab4f 100644
--- a/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java
+++ b/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java
@@ -17,7 +17,9 @@
 package org.apache.sling.commons.scheduler.impl;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -28,6 +30,7 @@
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.commons.scheduler.Job;
@@ -62,9 +65,6 @@
 @Service(value=QuartzScheduler.class)
 public class QuartzScheduler implements BundleListener {
 
-    /** Default logger. */
-    private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
     /** Map key for the job object */
     static final String DATA_MAP_OBJECT = "QuartzJobScheduler.Object";
 
@@ -86,16 +86,31 @@
     /** Map key for the bundle information (Long). */
     static final String DATA_MAP_SERVICE_ID = "QuartzJobScheduler.serviceId";
 
-    /** The quartz scheduler. */
-    private volatile SchedulerProxy scheduler;
+    @Property(label="Thread Pool Name",
+            description="The name of a configured thread pool - if no name is configured " +
+                        "the default pool is used.")
+    private static final String PROPERTY_POOL_NAME = "poolName";
+
+    @Property(label="Allowed Thread Pools",
+             description="The names of thread pools that are allowed to be used by jobs. " +
+                        "If a job is using a pool not in this list, the default pool is used.",
+             unbounded=PropertyUnbounded.ARRAY)
+    private static final String PROPERTY_ALLOWED_POOLS = "allowedPoolNames";
+
+    /** Default logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
     @Reference
     private ThreadPoolManager threadPoolManager;
 
-    @Property(label="Thread Pool Name",
-              description="The name of a configured thread pool - if no name is configured " +
-                          "the default pool is used.")
-    private static final String PROPERTY_POOL_NAME = "poolName";
+    /** The quartz schedulers. */
+    private final Map<String, SchedulerProxy> schedulers = new HashMap<String, SchedulerProxy>();
+
+    private volatile String defaultPoolName;
+
+    private volatile String[] allowedPoolNames;
+
+    private volatile boolean active;
 
     /**
      * Activate this component.
@@ -103,22 +118,32 @@
      * @throws Exception
      */
     @Activate
-    protected void activate(final BundleContext ctx, final Map<String, Object> props) throws Exception {
+    protected void activate(final BundleContext ctx, final Map<String, Object> props) {
         // SLING-2261 Prevent Quartz from checking for updates
         System.setProperty("org.terracotta.quartz.skipUpdateCheck", Boolean.TRUE.toString());
 
         final Object poolNameObj = props.get(PROPERTY_POOL_NAME);
-        final String poolName;
         if ( poolNameObj != null && poolNameObj.toString().trim().length() > 0 ) {
-            poolName = poolNameObj.toString().trim();
+            this.defaultPoolName = poolNameObj.toString().trim();
         } else {
-            poolName = null;
+            this.defaultPoolName = ThreadPoolManager.DEFAULT_THREADPOOL_NAME;
         }
-
+        final Object value = props.get(PROPERTY_ALLOWED_POOLS);
+        if ( value instanceof String[] ) {
+            this.allowedPoolNames = (String[])value;
+        } else if ( value != null ) {
+            this.allowedPoolNames = new String[] {value.toString()};
+        }
+        for(int i=0;i<this.allowedPoolNames.length;i++) {
+            if ( this.allowedPoolNames[i] == null ) {
+                this.allowedPoolNames[i] = "";
+            } else {
+                this.allowedPoolNames[i] = this.allowedPoolNames[i].trim();
+            }
+        }
         ctx.addBundleListener(this);
 
-        // start scheduler
-        this.scheduler = new SchedulerProxy(this.threadPoolManager, getThreadPoolName(poolName));
+        this.active = true;
     }
 
     /**
@@ -129,25 +154,42 @@
     protected void deactivate(final BundleContext ctx) {
         ctx.removeBundleListener(this);
 
-        final SchedulerProxy s = this.scheduler;
-        this.scheduler = null;
-        if ( s != null ) {
-            s.dispose();
+        final Map<String, SchedulerProxy> proxies;
+        synchronized ( this.schedulers ) {
+            this.active = false;
+            proxies = new HashMap<String, SchedulerProxy>(this.schedulers);
+            this.schedulers.clear();
+        }
+        for(final SchedulerProxy proxy : proxies.values()) {
+            proxy.dispose();
         }
     }
 
     private String getThreadPoolName(final String name) {
         if ( name == null || name.trim().isEmpty() ) {
-            return ThreadPoolManager.DEFAULT_THREADPOOL_NAME;
+            return this.defaultPoolName;
         }
-        return name.trim();
+        for(final String n : this.allowedPoolNames) {
+            if ( name.trim().equals(n) ) {
+                return n;
+            }
+        }
+        return this.defaultPoolName;
     }
 
-    private org.quartz.Scheduler getScheduler(final SchedulerProxy proxy) {
-        if ( proxy != null ) {
-            return proxy.getScheduler();
+    private SchedulerProxy getScheduler(final String pName) throws SchedulerException {
+        final String poolName = getThreadPoolName(pName);
+        SchedulerProxy proxy = null;
+        synchronized ( this.schedulers ) {
+            if ( this.active ) {
+                proxy = this.schedulers.get(poolName);
+                if ( proxy == null ) {
+                    proxy = new SchedulerProxy(this.threadPoolManager, poolName);
+                    this.schedulers.put(poolName, proxy);
+                }
+            }
         }
-        return null;
+        return proxy;
     }
 
     /**
@@ -158,15 +200,22 @@
         if ( event.getType() == BundleEvent.STOPPED ) {
             final Long bundleId = event.getBundle().getBundleId();
 
-            final org.quartz.Scheduler s = getScheduler(this.scheduler);
-            if ( s != null ) {
-                synchronized ( this ) {
+            final Map<String, SchedulerProxy> proxies;
+            synchronized ( this.schedulers ) {
+                if ( this.active ) {
+                    proxies = new HashMap<String, SchedulerProxy>(this.schedulers);
+                } else {
+                    proxies = Collections.emptyMap();
+                }
+            }
+            for(final SchedulerProxy proxy : proxies.values()) {
+                synchronized ( proxy ) {
                     try {
-                        final List<String> groups = s.getJobGroupNames();
+                        final List<String> groups = proxy.getScheduler().getJobGroupNames();
                         for(final String group : groups) {
-                            final Set<JobKey> keys = s.getJobKeys(GroupMatcher.jobGroupEquals(group));
+                            final Set<JobKey> keys = proxy.getScheduler().getJobKeys(GroupMatcher.jobGroupEquals(group));
                             for(final JobKey key : keys) {
-                                final JobDetail detail = s.getJobDetail(key);
+                                final JobDetail detail = proxy.getScheduler().getJobDetail(key);
                                 if ( detail != null ) {
                                     final String jobName = (String) detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_NAME);
                                     final Object job = detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_OBJECT);
@@ -174,7 +223,7 @@
                                     if ( jobName != null && job != null ) {
                                         final Long jobBundleId = (Long) detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_BUNDLE_ID);
                                         if ( jobBundleId != null && jobBundleId.equals(bundleId) ) {
-                                            s.deleteJob(key);
+                                            proxy.getScheduler().deleteJob(key);
                                             this.logger.debug("Unscheduling job with name {}", jobName);
                                         }
                                     }
@@ -365,30 +414,36 @@
     /**
      * @see org.apache.sling.commons.scheduler.Scheduler#removeJob(java.lang.String)
      */
-    public void removeJob(final Long bundleId, final String name) throws NoSuchElementException {
+    public void removeJob(final Long bundleId, final String jobName) throws NoSuchElementException {
         // as this method might be called from unbind and during
         // unbind a deactivate could happen, we check the scheduler first
-        final org.quartz.Scheduler s = this.getScheduler(this.scheduler);
-        if ( s != null ) {
-            synchronized ( this ) {
+        final Map<String, SchedulerProxy> proxies;
+        synchronized ( this.schedulers ) {
+            if ( this.active ) {
+                proxies = new HashMap<String, SchedulerProxy>(this.schedulers);
+            } else {
+                proxies = Collections.emptyMap();
+            }
+        }
+        for(final SchedulerProxy proxy : proxies.values()) {
+            synchronized ( proxy ) {
                 try {
-                    s.deleteJob(JobKey.jobKey(name));
-                    this.logger.debug("Unscheduling job with name {}", name);
-                } catch (final SchedulerException se) {
-                    throw new NoSuchElementException(se.getMessage());
+                    final JobKey key = JobKey.jobKey(jobName);
+                    final JobDetail jobdetail = proxy.getScheduler().getJobDetail(key);
+                    if (jobdetail != null) {
+                        proxy.getScheduler().deleteJob(key);
+                        this.logger.debug("Unscheduling job with name {}", jobName);
+                        return;
+                    }
+                } catch (final SchedulerException ignored) {
+                    // ignore
                 }
             }
         }
+        throw new NoSuchElementException("No job found with name " + jobName);
     }
 
-    /** Used by the web console plugin. */
-    org.quartz.Scheduler getScheduler() {
-        return this.getScheduler(this.scheduler);
-    }
-
-
-
-    /**
+   /**
      * @see org.apache.sling.commons.scheduler.Scheduler#NOW()
      */
     public ScheduleOptions NOW() {
@@ -487,19 +542,24 @@
      * @see org.apache.sling.commons.scheduler.Scheduler#unschedule(java.lang.String)
      */
     public boolean unschedule(final Long bundleId, final String jobName) {
-        final org.quartz.Scheduler s = this.getScheduler(this.scheduler);
-        if ( jobName != null && s != null ) {
-            synchronized ( this ) {
-                try {
-                    final JobKey key = JobKey.jobKey(jobName);
-                    final JobDetail jobdetail = s.getJobDetail(key);
-                    if (jobdetail != null) {
-                        s.deleteJob(key);
-                        this.logger.debug("Unscheduling job with name {}", jobName);
-                        return true;
+        if ( jobName != null ) {
+            final Map<String, SchedulerProxy> proxies;
+            synchronized ( this.schedulers ) {
+                proxies = new HashMap<String, SchedulerProxy>(this.schedulers);
+            }
+            for(final SchedulerProxy proxy : proxies.values()) {
+                synchronized ( proxy ) {
+                    try {
+                        final JobKey key = JobKey.jobKey(jobName);
+                        final JobDetail jobdetail = proxy.getScheduler().getJobDetail(key);
+                        if (jobdetail != null) {
+                            proxy.getScheduler().deleteJob(key);
+                            this.logger.debug("Unscheduling job with name {}", jobName);
+                            return true;
+                        }
+                    } catch (final SchedulerException ignored) {
+                        // ignore
                     }
-                } catch (final SchedulerException ignored) {
-                    // ignore
                 }
             }
         }
@@ -526,25 +586,16 @@
 
         // as this method might be called from unbind and during
         // unbind a deactivate could happen, we check the scheduler first
-        final org.quartz.Scheduler s = this.getScheduler(this.scheduler);
-        if ( s == null ) {
+        final SchedulerProxy proxy = this.getScheduler(opts.threadPoolName);
+        if ( proxy == null ) {
             throw new IllegalStateException("Scheduler is not available anymore.");
         }
 
-        synchronized ( this ) {
+        synchronized ( proxy ) {
             final String name;
             if ( opts.name != null ) {
                 // if there is already a job with the name, remove it first
-                try {
-                    final JobKey key = JobKey.jobKey(opts.name);
-                    final JobDetail jobdetail = s.getJobDetail(key);
-                    if (jobdetail != null) {
-                        s.deleteJob(key);
-                        this.logger.debug("Unscheduling job with name {}", opts.name);
-                    }
-                } catch (final SchedulerException ignored) {
-                    // ignore
-                }
+                this.unschedule(bundleId, opts.name);
                 name = opts.name;
             } else {
                 name = job.getClass().getName() + ':' + UUID.randomUUID();
@@ -558,7 +609,17 @@
             final JobDetail detail = this.createJobDetail(name, jobDataMap, opts.canRunConcurrently);
 
             this.logger.debug("Scheduling job {} with name {} and trigger {}", new Object[] {job, name, trigger});
-            s.scheduleJob(detail, trigger);
+            proxy.getScheduler().scheduleJob(detail, trigger);
+        }
+    }
+
+    /**
+     * This is used by the web console plugin
+     * @return All current schedulers
+     */
+    Map<String, SchedulerProxy> getSchedulers() {
+        synchronized ( this.schedulers ) {
+            return new HashMap<String, SchedulerProxy>(this.schedulers);
         }
     }
 }
diff --git a/src/main/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinter.java b/src/main/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinter.java
index b4dfefd..84e643d 100644
--- a/src/main/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinter.java
+++ b/src/main/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinter.java
@@ -21,6 +21,7 @@
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.felix.scr.annotations.Component;
@@ -64,93 +65,99 @@
     public void printConfiguration(PrintWriter pw) {
         pw.println(HEADLINE);
         pw.println();
-        final Scheduler s = this.scheduler.getScheduler();
-        if ( s != null ) {
+        final Map<String, SchedulerProxy> proxies = this.scheduler.getSchedulers();
+        if ( !proxies.isEmpty() ) {
             pw.println("Status : active");
-            try {
-                pw.print  ("Name   : ");
-                pw.println(s.getSchedulerName());
-                pw.print  ("Id     : ");
-                pw.println(s.getSchedulerInstanceId());
-                pw.println();
-                final List<String> groups = s.getJobGroupNames();
-                for(final String group : groups) {
-                    final Set<JobKey> keys = s.getJobKeys(GroupMatcher.jobGroupEquals(group));
-                    for(final JobKey key : keys) {
-                        final JobDetail detail = s.getJobDetail(key);
-                        final String jobName = (String) detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_NAME);
-                        final Object job = detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_OBJECT);
-                        // only print jobs started through the sling scheduler
-                        if ( jobName != null && job != null ) {
-                            pw.print("Job : ");
-                            pw.print(detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_NAME));
-                            if ( detail.getDescription() != null && detail.getDescription().length() > 0 ) {
-                                pw.print(" (");
-                                pw.print(detail.getDescription());
-                                pw.print(")");
-                            }
-                            pw.print(", class: ");
-                            pw.print(job.getClass().getName());
-                            pw.print(", concurrent: ");
-                            pw.print(!detail.isConcurrentExectionDisallowed());
-                            final String[] runOn = (String[])detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_RUN_ON);
-                            if ( runOn != null ) {
-                                pw.print(", runOn: ");
-                                pw.print(Arrays.toString(runOn));
-                                // check run on information
-                                if ( runOn.length == 1 &&
-                                     (org.apache.sling.commons.scheduler.Scheduler.VALUE_RUN_ON_LEADER.equals(runOn[0]) || org.apache.sling.commons.scheduler.Scheduler.VALUE_RUN_ON_SINGLE.equals(runOn[0])) ) {
-                                    if ( QuartzJobExecutor.DISCOVERY_AVAILABLE.get() ) {
-                                        if ( QuartzJobExecutor.DISCOVERY_INFO_AVAILABLE.get() ) {
-                                            if ( !QuartzJobExecutor.IS_LEADER.get() ) {
-                                                pw.print(" (inactive: not leader)");
+            for(final Map.Entry<String, SchedulerProxy> entry : proxies.entrySet()) {
+                final Scheduler s = entry.getValue().getScheduler();
+                try {
+                    pw.print  ("Name      : ");
+                    pw.println(s.getSchedulerName());
+                    pw.print  ("ThreadPool: ");
+                    pw.println(entry.getKey());
+                    pw.print  ("Id        : ");
+                    pw.println(s.getSchedulerInstanceId());
+                    pw.println();
+                    final List<String> groups = s.getJobGroupNames();
+                    for(final String group : groups) {
+                        final Set<JobKey> keys = s.getJobKeys(GroupMatcher.jobGroupEquals(group));
+                        for(final JobKey key : keys) {
+                            final JobDetail detail = s.getJobDetail(key);
+                            final String jobName = (String) detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_NAME);
+                            final Object job = detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_OBJECT);
+                            // only print jobs started through the sling scheduler
+                            if ( jobName != null && job != null ) {
+                                pw.print("Job : ");
+                                pw.print(detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_NAME));
+                                if ( detail.getDescription() != null && detail.getDescription().length() > 0 ) {
+                                    pw.print(" (");
+                                    pw.print(detail.getDescription());
+                                    pw.print(")");
+                                }
+                                pw.print(", class: ");
+                                pw.print(job.getClass().getName());
+                                pw.print(", concurrent: ");
+                                pw.print(!detail.isConcurrentExectionDisallowed());
+                                final String[] runOn = (String[])detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_RUN_ON);
+                                if ( runOn != null ) {
+                                    pw.print(", runOn: ");
+                                    pw.print(Arrays.toString(runOn));
+                                    // check run on information
+                                    if ( runOn.length == 1 &&
+                                         (org.apache.sling.commons.scheduler.Scheduler.VALUE_RUN_ON_LEADER.equals(runOn[0]) || org.apache.sling.commons.scheduler.Scheduler.VALUE_RUN_ON_SINGLE.equals(runOn[0])) ) {
+                                        if ( QuartzJobExecutor.DISCOVERY_AVAILABLE.get() ) {
+                                            if ( QuartzJobExecutor.DISCOVERY_INFO_AVAILABLE.get() ) {
+                                                if ( !QuartzJobExecutor.IS_LEADER.get() ) {
+                                                    pw.print(" (inactive: not leader)");
+                                                }
+                                            } else {
+                                                pw.print(" (inactive: no discovery info)");
                                             }
                                         } else {
-                                            pw.print(" (inactive: no discovery info)");
+                                            pw.print(" (inactive: no discovery)");
                                         }
-                                    } else {
-                                        pw.print(" (inactive: no discovery)");
-                                    }
-                                } else { // sling IDs
-                                    final String myId = QuartzJobExecutor.SLING_ID;
-                                    if ( myId == null ) {
-                                        pw.print(" (inactive: no Sling settings)");
-                                    } else {
-                                        boolean schedule = false;
-                                        for(final String id : runOn ) {
-                                            if ( myId.equals(id) ) {
-                                                schedule = true;
-                                                break;
+                                    } else { // sling IDs
+                                        final String myId = QuartzJobExecutor.SLING_ID;
+                                        if ( myId == null ) {
+                                            pw.print(" (inactive: no Sling settings)");
+                                        } else {
+                                            boolean schedule = false;
+                                            for(final String id : runOn ) {
+                                                if ( myId.equals(id) ) {
+                                                    schedule = true;
+                                                    break;
+                                                }
+                                            }
+                                            if ( !schedule ) {
+                                                pw.print(" (inactive: Sling ID)");
                                             }
                                         }
-                                        if ( !schedule ) {
-                                            pw.print(" (inactive: Sling ID)");
-                                        }
-                                    }
-                                }                            }
-                            final Long bundleId = (Long)detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_BUNDLE_ID);
-                            if ( bundleId != null ) {
-                                pw.print(", bundleId: ");
-                                pw.print(String.valueOf(bundleId));
-                            }
-                            final Long serviceId = (Long)detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_SERVICE_ID);
-                            if ( serviceId != null ) {
-                                pw.print(", serviceId: ");
-                                pw.print(String.valueOf(serviceId));
-                            }
-                            pw.println();
-                            for(final Trigger trigger : s.getTriggersOfJob(key)) {
-                                pw.print("Trigger : ");
-                                pw.print(trigger);
+                                    }                            }
+                                final Long bundleId = (Long)detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_BUNDLE_ID);
+                                if ( bundleId != null ) {
+                                    pw.print(", bundleId: ");
+                                    pw.print(String.valueOf(bundleId));
+                                }
+                                final Long serviceId = (Long)detail.getJobDataMap().get(QuartzScheduler.DATA_MAP_SERVICE_ID);
+                                if ( serviceId != null ) {
+                                    pw.print(", serviceId: ");
+                                    pw.print(String.valueOf(serviceId));
+                                }
+                                pw.println();
+                                for(final Trigger trigger : s.getTriggersOfJob(key)) {
+                                    pw.print("Trigger : ");
+                                    pw.print(trigger);
+                                    pw.println();
+                                }
                                 pw.println();
                             }
-                            pw.println();
                         }
                     }
+                } catch ( final SchedulerException se ) {
+                    pw.print  ("Unable to print complete configuration: ");
+                    pw.println(se.getMessage());
                 }
-            } catch ( final SchedulerException se ) {
-                pw.print  ("Unable to print complete configuration: ");
-                pw.println(se.getMessage());
+                pw.println();
             }
         } else {
             pw.println("Status : not active");
diff --git a/src/test/java/org/apache/sling/commons/scheduler/impl/ActivatedQuartzSchedulerFactory.java b/src/test/java/org/apache/sling/commons/scheduler/impl/ActivatedQuartzSchedulerFactory.java
index 14316e0..f0aa0cd 100644
--- a/src/test/java/org/apache/sling/commons/scheduler/impl/ActivatedQuartzSchedulerFactory.java
+++ b/src/test/java/org/apache/sling/commons/scheduler/impl/ActivatedQuartzSchedulerFactory.java
@@ -16,21 +16,22 @@
  */
 package org.apache.sling.commons.scheduler.impl;
 
-import org.apache.sling.commons.threads.impl.DefaultThreadPoolManager;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-
 import java.lang.reflect.Field;
 import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Map;
 
+import org.apache.sling.commons.threads.impl.DefaultThreadPoolManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
 /**
  * This is just a class with helper static method,
  * since we need an activated QuartzScheduler in many tests.
  */
 class ActivatedQuartzSchedulerFactory {
+
     public static QuartzScheduler create(BundleContext context, String poolName) throws Exception {
         QuartzScheduler quartzScheduler = null;
         if (context != null) {
@@ -45,6 +46,9 @@
 
             Map<String, Object> scheduleActivationProps = new HashMap<String, Object>();
             scheduleActivationProps.put("poolName", poolName == null ? "testName" : poolName);
+            if ( poolName != null ) {
+                scheduleActivationProps.put("allowedPoolNames", new String[] {"testName", "allowed"});
+            }
 
             quartzScheduler.activate(context, scheduleActivationProps);
             context.registerService("scheduler", quartzScheduler, props);
diff --git a/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutorTest.java b/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutorTest.java
index c26659e..4a5bb32 100644
--- a/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutorTest.java
+++ b/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutorTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.when;
 
 import java.io.Serializable;
-import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -40,12 +39,10 @@
 import org.quartz.JobDetail;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobKey;
-import org.quartz.Scheduler;
 import org.quartz.SchedulerException;
 
 @RunWith(MockitoJUnitRunner.class)
 public class QuartzJobExecutorTest {
-    private Scheduler scheduler;
     private BundleContext context;
     private QuartzJobExecutor jobExecutor;
     private QuartzScheduler quartzScheduler;
@@ -60,10 +57,6 @@
         jobExecutor = new QuartzJobExecutor();
 
         quartzScheduler = ActivatedQuartzSchedulerFactory.create(context, "testName");
-
-        Field schedulerField = QuartzScheduler.class.getDeclaredField("scheduler");
-        schedulerField.setAccessible(true);
-        scheduler = ((SchedulerProxy) schedulerField.get(quartzScheduler)).getScheduler();
     }
 
     @Test
@@ -75,7 +68,7 @@
         //Adding a job just to receive a JobDetail object which is needed for testing
         quartzScheduler.addJob(1L, 1L, jobName, job, jobConfig, "0 * * * * ?", true);
 
-        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName));
+        JobDetail jobDetail = quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(JobKey.jobKey(jobName));
         when(executionContext.getJobDetail()).thenReturn(jobDetail);
 
         isRunnablePseudoJobCompleted = false;
@@ -99,7 +92,7 @@
         //Adding a job just to receive a JobDetail object which is needed for testing
         quartzScheduler.addJob(1L, 1L, jobName, job, jobConfig, "0 * * * * ?", true);
 
-        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName));
+        JobDetail jobDetail = quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(JobKey.jobKey(jobName));
         when(executionContext.getJobDetail()).thenReturn(jobDetail);
 
         isRunnablePseudoJobCompleted = false;
@@ -116,7 +109,7 @@
         //Adding a job just to receive a JobDetail object which is needed for testing
         quartzScheduler.addJob(1L, 1L, jobName, job, jobConfig, "0 * * * * ?", true);
 
-        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName));
+        JobDetail jobDetail = quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(JobKey.jobKey(jobName));
         when(executionContext.getJobDetail()).thenReturn(jobDetail);
         //Job with this config should not be executed
         jobDetail.getJobDataMap().put(QuartzScheduler.DATA_MAP_RUN_ON, new String[]{VALUE_RUN_ON_LEADER});
@@ -135,7 +128,7 @@
         //Adding a job just to receive a JobDetail object which is needed for testing
         quartzScheduler.addJob(1L, 1L, jobName, job, jobConfig, "0 * * * * ?", true);
 
-        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName));
+        JobDetail jobDetail = quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(JobKey.jobKey(jobName));
         when(executionContext.getJobDetail()).thenReturn(jobDetail);
         //Job with this config should not be executed
         jobDetail.getJobDataMap().put(QuartzScheduler.DATA_MAP_RUN_ON,
@@ -157,7 +150,7 @@
         //Adding a job just to receive a JobDetail object which is needed for testing
         quartzScheduler.addJob(1L, 1L, jobName, job, jobConfig, "0 * * * * ?", true);
 
-        JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName));
+        JobDetail jobDetail = quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(JobKey.jobKey(jobName));
         when(executionContext.getJobDetail()).thenReturn(jobDetail);
         //Job with this config should not be executed
         jobDetail.getJobDataMap().put(QuartzScheduler.DATA_MAP_RUN_ON,
@@ -181,6 +174,11 @@
         assertTrue(underTest.getName().equals(testName));
     }
 
+    @Test
+    public void testLazyScheduler() {
+        assertTrue(quartzScheduler.getSchedulers().isEmpty());
+    }
+
     @After
     public void deactivateScheduler() {
         quartzScheduler.deactivate(context);
diff --git a/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerTest.java b/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerTest.java
index 5949d90..afc4715 100644
--- a/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerTest.java
+++ b/src/test/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerTest.java
@@ -16,6 +16,19 @@
  */
 package org.apache.sling.commons.scheduler.impl;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.sling.commons.scheduler.Job;
 import org.apache.sling.testing.mock.osgi.MockOsgi;
 import org.junit.After;
@@ -30,23 +43,13 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleEvent;
 import org.quartz.JobKey;
-import org.quartz.Scheduler;
 import org.quartz.SchedulerException;
 import org.quartz.TriggerBuilder;
 
-import java.io.Serializable;
-import java.lang.reflect.Field;
-import java.util.Date;
-import java.util.HashMap;
-
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.when;
-
 @RunWith(MockitoJUnitRunner.class)
 public class QuartzSchedulerTest {
-    
-    private Scheduler s;
-    private SchedulerProxy proxy;
+
+    private Map<String, SchedulerProxy> proxies;
     private BundleContext context;
     private QuartzScheduler quartzScheduler;
 
@@ -56,14 +59,14 @@
     @Rule
     public ExpectedException thrown = ExpectedException.none();
 
+    @SuppressWarnings("unchecked")
     @Before
     public void setUp() throws Exception {
         context = MockOsgi.newBundleContext();
         quartzScheduler = ActivatedQuartzSchedulerFactory.create(context, "testName");
-        s = quartzScheduler.getScheduler();
-        Field sField = QuartzScheduler.class.getDeclaredField("scheduler");
+        Field sField = QuartzScheduler.class.getDeclaredField("schedulers");
         sField.setAccessible(true);
-        this.proxy = (SchedulerProxy) sField.get(quartzScheduler);
+        this.proxies = (Map<String, SchedulerProxy>) sField.get(quartzScheduler);
     }
 
     @Test
@@ -110,39 +113,28 @@
     }
 
     @Test
-    public void testWithoutScheduler() throws Exception {
-        setInternalSchedulerToNull();
-
-        thrown.expect(IllegalStateException.class);
-        thrown.expectMessage("Scheduler is not available anymore.");
-        quartzScheduler.addJob(1L, 1L, "testName", new Thread(), new HashMap<String, Serializable>(), "0 * * * * ?", true);
-
-        returnInternalSchedulerBack();
-    }
-
-    @Test
     public void testAddJob() throws SchedulerException {
         quartzScheduler.addJob(1L, 1L, "testName", new Thread(), new HashMap<String, Serializable>(), "0 * * * * ?", true);
-        assertTrue(s.checkExists(JobKey.jobKey("testName")));
-        assertFalse(s.checkExists(JobKey.jobKey("wrongName")));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("testName")));
+        assertFalse(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("wrongName")));
     }
 
     @Test
     public void testAddJobTwice() throws SchedulerException {
         quartzScheduler.addJob(1L, 1L, "testName", new Thread(), new HashMap<String, Serializable>(), "0 * * * * ?", true);
-        assertTrue(s.checkExists(JobKey.jobKey("testName")));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("testName")));
         //Add very same job twice to check that there is no conflicts and previous
         quartzScheduler.addJob(1L, 1L, "testName", new Thread(), new HashMap<String, Serializable>(), "0 * * * * ?", true);
-        assertTrue(s.checkExists(JobKey.jobKey("testName")));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("testName")));
     }
 
     @Test
     public void testRemoveJob() throws SchedulerException {
         String jobName = "testName";
         quartzScheduler.addJob(1L, 1L, jobName, new Thread(), new HashMap<String, Serializable>(), "0 * * * * ?", true);
-        assertTrue(s.checkExists(JobKey.jobKey(jobName)));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(jobName)));
         quartzScheduler.removeJob(1L, jobName);
-        assertFalse(s.checkExists(JobKey.jobKey(jobName)));
+        assertFalse(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(jobName)));
     }
 
     @Test
@@ -190,10 +182,10 @@
         String otherJobName = "anyOtherName";
 
         quartzScheduler.addPeriodicJob(4L, 4L, jobName, new Thread(), new HashMap(), 2L, true, true);
-        assertTrue("Job must exists", s.checkExists(JobKey.jobKey(jobName)));
+        assertTrue("Job must exists", proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(jobName)));
 
         quartzScheduler.addPeriodicJob(5L, 5L, otherJobName, new Thread(), new HashMap(), 2L, true, false);
-        assertTrue("Job must exists", s.checkExists(JobKey.jobKey(otherJobName)));
+        assertTrue("Job must exists", proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(otherJobName)));
     }
 
     @Test
@@ -229,8 +221,8 @@
         BundleEvent event = new BundleEvent(BundleEvent.STOPPED, bundle);
         quartzScheduler.bundleChanged(event);
 
-        assertTrue(s.checkExists(JobKey.jobKey(firstJob)));
-        assertFalse(s.checkExists(JobKey.jobKey(secondJob)));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(firstJob)));
+        assertFalse(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(secondJob)));
     }
 
     @Test
@@ -245,8 +237,8 @@
         BundleEvent event = new BundleEvent(BundleEvent.STARTED, bundle);
         quartzScheduler.bundleChanged(event);
 
-        assertTrue(s.checkExists(JobKey.jobKey(firstJob)));
-        assertTrue(s.checkExists(JobKey.jobKey(secondJob)));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(firstJob)));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(secondJob)));
     }
 
     @Test
@@ -264,31 +256,65 @@
         BundleEvent event = new BundleEvent(BundleEvent.STOPPED, bundle);
         quartzScheduler.bundleChanged(event);
 
-        assertTrue(s.checkExists(JobKey.jobKey(firstJob)));
-        assertTrue(s.checkExists(JobKey.jobKey(secondJob)));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(firstJob)));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey(secondJob)));
 
         returnInternalSchedulerBack();
     }
 
+    @Test
+    public void testThreadPools() throws SchedulerException {
+        quartzScheduler.schedule(1L, 1L, new Thread(), quartzScheduler.NOW().name("j1").threadPoolName("tp1"));
+        quartzScheduler.schedule(1L, 2L, new Thread(), quartzScheduler.NOW().name("j2").threadPoolName("tp2"));
+        quartzScheduler.schedule(1L, 2L, new Thread(), quartzScheduler.NOW().name("j3").threadPoolName("allowed"));
+
+        assertNull(proxies.get("tp1"));
+        assertNull(proxies.get("tp2"));
+        assertNotNull(proxies.get("allowed"));
+
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("j1")));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("j2")));
+        assertTrue(proxies.get("allowed").getScheduler().checkExists(JobKey.jobKey("j3")));
+    }
+
+    @Test
+    public void testNameAcrossPools() throws SchedulerException {
+        quartzScheduler.schedule(1L, 1L, new Thread(), quartzScheduler.NOW().name("j1").threadPoolName("tp1"));
+        assertNull(proxies.get("tp1"));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("j1")));
+
+        quartzScheduler.schedule(1L, 1L, new Thread(), quartzScheduler.NOW().name("j1").threadPoolName("allowed"));
+        assertFalse(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("j1")));
+        assertTrue(proxies.get("allowed").getScheduler().checkExists(JobKey.jobKey("j1")));
+
+        quartzScheduler.schedule(1L, 1L, new Thread(), quartzScheduler.NOW().name("j1"));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("j1")));
+        assertFalse(proxies.get("allowed").getScheduler().checkExists(JobKey.jobKey("j1")));
+
+        quartzScheduler.schedule(1L, 1L, new Thread(), quartzScheduler.NOW().name("j1").threadPoolName("tp1"));
+        assertNull(proxies.get("tp1"));
+        assertTrue(proxies.get("testName").getScheduler().checkExists(JobKey.jobKey("j1")));
+    }
+
     @After
     public void deactivateScheduler() throws NoSuchFieldException, IllegalAccessException {
-        if (quartzScheduler.getScheduler() == null) {
+        if (quartzScheduler.getSchedulers().isEmpty()) {
             returnInternalSchedulerBack();
         }
         quartzScheduler.deactivate(context);
     }
 
     private void setInternalSchedulerToNull() throws NoSuchFieldException, IllegalAccessException {
-        Field sField = QuartzScheduler.class.getDeclaredField("scheduler");
+        Field sField = QuartzScheduler.class.getDeclaredField("schedulers");
         sField.setAccessible(true);
-        sField.set(quartzScheduler, null);
+        sField.set(quartzScheduler, new HashMap<String, SchedulerProxy>());
     }
 
     private void returnInternalSchedulerBack() throws NoSuchFieldException, IllegalAccessException {
-        Field sField = QuartzScheduler.class.getDeclaredField("scheduler");
+        Field sField = QuartzScheduler.class.getDeclaredField("schedulers");
         sField.setAccessible(true);
-        if (quartzScheduler.getScheduler() == null && proxy != null) {
-            sField.set(quartzScheduler, proxy);
+        if (quartzScheduler.getSchedulers().isEmpty() && this.proxies != null) {
+            sField.set(quartzScheduler, this.proxies);
         }
     }
 }
diff --git a/src/test/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinterTest.java b/src/test/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinterTest.java
index 171ccf5..8ff225d 100644
--- a/src/test/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinterTest.java
+++ b/src/test/java/org/apache/sling/commons/scheduler/impl/WebConsolePrinterTest.java
@@ -16,6 +16,18 @@
  */
 package org.apache.sling.commons.scheduler.impl;
 
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
 import org.apache.sling.testing.mock.osgi.MockOsgi;
 import org.junit.After;
 import org.junit.Before;
@@ -23,13 +35,6 @@
 import org.osgi.framework.BundleContext;
 import org.quartz.SchedulerException;
 
-import java.io.*;
-import java.lang.reflect.Field;
-import java.util.HashMap;
-import java.util.regex.Pattern;
-
-import static org.junit.Assert.assertTrue;
-
 public class WebConsolePrinterTest {
     private WebConsolePrinter consolePrinter;
     private QuartzScheduler quartzScheduler;
@@ -64,6 +69,7 @@
             reader.readLine();
             assertRegexp(reader.readLine(), ".*Status.*active.*");
             assertRegexp(reader.readLine(), ".*Name.*ApacheSling.*");
+            assertRegexp(reader.readLine(), ".*ThreadPool.*testName.*");
             assertRegexp(reader.readLine(), ".*Id.*");
             reader.readLine();
             assertRegexp(reader.readLine(), "^Job.*testName[123].*");
@@ -78,7 +84,7 @@
             reader.close();
         }
     }
-    
+
     private void assertRegexp(String input, String regexp) {
         assertTrue("Expecting regexp match: '" + input + "' / '" + regexp + "'", Pattern.matches(regexp, input));
     }
diff --git a/src/test/java/org/apache/sling/commons/scheduler/impl/WhiteboardHandlerTest.java b/src/test/java/org/apache/sling/commons/scheduler/impl/WhiteboardHandlerTest.java
index 02f0a41..942deaf 100644
--- a/src/test/java/org/apache/sling/commons/scheduler/impl/WhiteboardHandlerTest.java
+++ b/src/test/java/org/apache/sling/commons/scheduler/impl/WhiteboardHandlerTest.java
@@ -16,6 +16,13 @@
  */
 package org.apache.sling.commons.scheduler.impl;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.lang.reflect.Field;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
 import org.apache.sling.commons.scheduler.Scheduler;
 import org.apache.sling.testing.mock.osgi.MockOsgi;
 import org.junit.After;
@@ -29,13 +36,6 @@
 import org.quartz.JobKey;
 import org.quartz.SchedulerException;
 
-import java.lang.reflect.Field;
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
 public class WhiteboardHandlerTest {
     private WhiteboardHandler handler;
     private BundleContext context;
@@ -71,7 +71,6 @@
 
     @Test
     public void testAddingService() throws SchedulerException {
-        org.quartz.Scheduler s = quartzScheduler.getScheduler();
         Thread service = new Thread();
         String serviceName = "serviceName";
         String schedulerName = "testScheduler";
@@ -94,14 +93,13 @@
         ServiceReference reference = context.getServiceReference(serviceName);
         JobKey jobKey = JobKey.jobKey(schedulerName + "." + reference.getProperty(Constants.SERVICE_ID));
 
-        assertNull(s.getJobDetail(jobKey));
+        assertNull(quartzScheduler.getSchedulers().get("testName"));
         customizer.addingService(reference);
-        assertNotNull(s.getJobDetail(jobKey));
+        assertNotNull(quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(jobKey));
     }
 
     @Test
     public void testUnregisterService() throws SchedulerException {
-        org.quartz.Scheduler s = quartzScheduler.getScheduler();
         Thread service = new Thread();
         String serviceName = "serviceName";
         String schedulerName = "testScheduler";
@@ -125,11 +123,11 @@
         ServiceReference reference = context.getServiceReference(serviceName);
         JobKey jobKey = JobKey.jobKey(schedulerName + "." + reference.getProperty(Constants.SERVICE_ID));
 
-        assertNull(s.getJobDetail(jobKey));
+        assertNull(quartzScheduler.getSchedulers().get("testName"));
         customizer.addingService(reference);
-        assertNotNull(s.getJobDetail(jobKey));
+        assertNotNull(quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(jobKey));
         customizer.removedService(reference, service);
-        assertNull(s.getJobDetail(jobKey));
+        assertNull(quartzScheduler.getSchedulers().get("testName").getScheduler().getJobDetail(jobKey));
     }
 
     @After