You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2014/05/13 13:03:21 UTC

[2/2] git commit: [KARAF-751] Provide a quartz based scheduler

[KARAF-751] Provide a quartz based scheduler

Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/eb61abc8
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/eb61abc8
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/eb61abc8

Branch: refs/heads/master
Commit: eb61abc898f3ceac3a98e59fe26ee94eb7ad1f65
Parents: 176e3a7
Author: Guillaume Nodet <gn...@gmail.com>
Authored: Tue May 13 12:26:36 2014 +0200
Committer: Guillaume Nodet <gn...@gmail.com>
Committed: Tue May 13 12:26:36 2014 +0200

----------------------------------------------------------------------
 scheduler/NOTICE                                |  71 +++++
 scheduler/core/NOTICE                           |  71 -----
 scheduler/core/pom.xml                          |  82 ------
 .../apache/karaf/scheduler/core/Activator.java  |  49 ----
 .../karaf/scheduler/core/KarafTimerTask.java    |  82 ------
 .../scheduler/core/RunnableServiceListener.java |  85 ------
 .../karaf/scheduler/core/TaskScheduler.java     |  54 ----
 .../src/main/resources/OSGI-INF/bundle.info     |  20 --
 scheduler/pom.xml                               |  95 +++++-
 .../java/org/apache/karaf/scheduler/Job.java    |  34 +++
 .../org/apache/karaf/scheduler/JobContext.java  |  38 +++
 .../apache/karaf/scheduler/ScheduleOptions.java |  59 ++++
 .../org/apache/karaf/scheduler/Scheduler.java   | 130 ++++++++
 .../apache/karaf/scheduler/command/List.java    |  48 +++
 .../apache/karaf/scheduler/core/Activator.java  |  51 ++++
 .../scheduler/core/InternalScheduleOptions.java | 160 ++++++++++
 .../core/NonParallelQuartzJobExecutor.java      |  31 ++
 .../karaf/scheduler/core/QuartzJobExecutor.java |  89 ++++++
 .../karaf/scheduler/core/QuartzScheduler.java   | 294 +++++++++++++++++++
 .../karaf/scheduler/core/WhiteboardHandler.java | 143 +++++++++
 .../src/main/resources/OSGI-INF/bundle.info     |  23 ++
 .../karaf/util/tracker/BaseActivator.java       |   1 +
 22 files changed, 1259 insertions(+), 451 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/NOTICE
----------------------------------------------------------------------
diff --git a/scheduler/NOTICE b/scheduler/NOTICE
new file mode 100644
index 0000000..b70f1f9
--- /dev/null
+++ b/scheduler/NOTICE
@@ -0,0 +1,71 @@
+Apache Karaf
+Copyright 2010-2014 The Apache Software Foundation
+
+
+I. Included Software
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2010).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+OW2 (http://www.ow2.org/).
+Licensed under the BSD License.
+
+This product includes software developed at
+OPS4J (http://www.ops4j.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+Eclipse Foundation (http://www.eclipse.org/).
+Licensed under the EPL.
+
+This product includes software written by
+Antony Lesuisse.
+Licensed under Public Domain.
+
+
+II. Used Software
+
+This product uses software developed at
+FUSE Source (http://www.fusesource.org/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+AOP Alliance (http://aopalliance.sourceforge.net/).
+Licensed under the Public Domain.
+
+This product uses software developed at
+Tanuki Software (http://www.tanukisoftware.com/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+Jasypt (http://jasypt.sourceforge.net/).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+JLine (http://jline.sourceforge.net).
+Licensed under the BSD License.
+
+This product uses software developed at
+SLF4J (http://www.slf4j.org/).
+Licensed under the MIT License.
+
+This product uses software developed at
+SpringSource (http://www.springsource.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software from http://www.json.org.
+Copyright (c) 2002 JSON.org
+
+
+III. License Summary
+- Apache License 2.0
+- BSD License
+- EPL License
+- MIT License

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/core/NOTICE
----------------------------------------------------------------------
diff --git a/scheduler/core/NOTICE b/scheduler/core/NOTICE
deleted file mode 100644
index b70f1f9..0000000
--- a/scheduler/core/NOTICE
+++ /dev/null
@@ -1,71 +0,0 @@
-Apache Karaf
-Copyright 2010-2014 The Apache Software Foundation
-
-
-I. Included Software
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-The OSGi Alliance (http://www.osgi.org/).
-Copyright (c) OSGi Alliance (2000, 2010).
-Licensed under the Apache License 2.0.
-
-This product includes software developed at
-OW2 (http://www.ow2.org/).
-Licensed under the BSD License.
-
-This product includes software developed at
-OPS4J (http://www.ops4j.org/).
-Licensed under the Apache License 2.0.
-
-This product includes software developed at
-Eclipse Foundation (http://www.eclipse.org/).
-Licensed under the EPL.
-
-This product includes software written by
-Antony Lesuisse.
-Licensed under Public Domain.
-
-
-II. Used Software
-
-This product uses software developed at
-FUSE Source (http://www.fusesource.org/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-AOP Alliance (http://aopalliance.sourceforge.net/).
-Licensed under the Public Domain.
-
-This product uses software developed at
-Tanuki Software (http://www.tanukisoftware.com/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-Jasypt (http://jasypt.sourceforge.net/).
-Licensed under the Apache License 2.0.
-
-This product uses software developed at
-JLine (http://jline.sourceforge.net).
-Licensed under the BSD License.
-
-This product uses software developed at
-SLF4J (http://www.slf4j.org/).
-Licensed under the MIT License.
-
-This product uses software developed at
-SpringSource (http://www.springsource.org/).
-Licensed under the Apache License 2.0.
-
-This product includes software from http://www.json.org.
-Copyright (c) 2002 JSON.org
-
-
-III. License Summary
-- Apache License 2.0
-- BSD License
-- EPL License
-- MIT License

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/core/pom.xml
----------------------------------------------------------------------
diff --git a/scheduler/core/pom.xml b/scheduler/core/pom.xml
deleted file mode 100644
index 8d06015..0000000
--- a/scheduler/core/pom.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
-    <!--
-
-        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.
-    -->
-
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <artifactId>scheduler</artifactId>
-        <groupId>org.apache.karaf</groupId>
-        <version>4.0.0-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-
-    <groupId>org.apache.karaf.scheduler</groupId>
-    <artifactId>org.apache.karaf.scheduler.core</artifactId>
-    <packaging>bundle</packaging>
-    <name>Apache Karaf :: Scheduler :: Core</name>
-    <description>This bundle provides a Service Listener which listnes for Runnable Services and schedules their execution, based  on the service properties</description>
-
-    <build>
-        <resources>
-            <resource>
-                <directory>${project.basedir}/src/main/resources</directory>
-                <includes>
-                    <include>**/*</include>
-                </includes>
-            </resource>
-            <resource>
-                <directory>${project.basedir}/src/main/resources</directory>
-                <filtering>true</filtering>
-                <includes>
-                    <include>**/*.info</include>
-                </includes>
-            </resource>
-        </resources>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <configuration>
-                    <instructions>
-                        <Bundle-Activator>org.apache.karaf.scheduler.core.Activator</Bundle-Activator>
-                        <Import-Package>
-                            *
-                        </Import-Package>
-                    </instructions>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>org.osgi.core</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-    </dependencies>
-
-</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/Activator.java
----------------------------------------------------------------------
diff --git a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/Activator.java b/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/Activator.java
deleted file mode 100644
index 30d310e..0000000
--- a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/Activator.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.karaf.scheduler.core;
-
-import org.osgi.framework.*;
-
-import java.util.Dictionary;
-import java.util.Properties;
-
-public class Activator implements BundleActivator {
-
-    private TaskScheduler scheduler = new TaskScheduler();
-    private RunnableServiceListener listener;
-
-    private static final String filter = "(&(objectclass=%s)(&(%s >= 0)(%s >= 0)))";
-
-    @Override
-    public void start(BundleContext context) throws Exception {
-        listener = new RunnableServiceListener(context, scheduler);
-
-        //register scheduler service
-        context.registerService(scheduler.getClass().getName(), scheduler, (Dictionary) new Properties());
-
-        //register service listener
-        context.addServiceListener(listener, String.format(filter, Runnable.class.getName(), KarafTimerTask.ID_PROPERTY, KarafTimerTask.PERIOD_PROPERTY));
-
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/KarafTimerTask.java
----------------------------------------------------------------------
diff --git a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/KarafTimerTask.java b/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/KarafTimerTask.java
deleted file mode 100644
index 5e1af7d..0000000
--- a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/KarafTimerTask.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.karaf.scheduler.core;
-
-import java.util.TimerTask;
-
-public class KarafTimerTask<R extends Runnable> extends TimerTask {
-
-    public static final String ID_PROPERTY = "org.apache.karaf.scheduler.task.id";
-    public static final String PERIOD_PROPERTY = "org.apache.karaf.scheduler.task.period";
-
-    protected String id;
-    protected R task;
-    protected Long schedulePeriod = 0L;
-
-    /**
-     * Constructor
-     * @param id  the id of the task. Used for reference.
-     * @param task the task to be scheduled
-     */
-    public KarafTimerTask(String id, R task) {
-        this.id = id;
-        this.task = task;
-    }
-
-    /**
-     * Constructor
-     * @param id  the id of the task. Used for reference.
-     * @param task the task to be scheduled
-     * @param schedulePeriod the schedule period.
-     */
-    public KarafTimerTask(String id, R task, Long schedulePeriod) {
-        this.id = id;
-        this.task = task;
-        this.schedulePeriod = schedulePeriod;
-    }
-
-    @Override
-    public void run() {
-        task.run();
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
-    public R getTask() {
-        return task;
-    }
-
-    public void setTask(R task) {
-        this.task = task;
-    }
-
-    public Long getSchedulePeriod() {
-        return schedulePeriod;
-    }
-
-    public void setSchedulePeriod(Long schedulePeriod) {
-        this.schedulePeriod = schedulePeriod;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/RunnableServiceListener.java
----------------------------------------------------------------------
diff --git a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/RunnableServiceListener.java b/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/RunnableServiceListener.java
deleted file mode 100644
index a1e8822..0000000
--- a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/RunnableServiceListener.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.karaf.scheduler.core;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceEvent;
-import org.osgi.framework.ServiceListener;
-import org.osgi.framework.ServiceReference;
-
-public class RunnableServiceListener implements ServiceListener {
-
-    private final BundleContext bundleContext;
-    private final TaskScheduler scheduler;
-
-    /**
-     * Constructor
-     *
-     * @param bundleContext
-     * @param scheduler
-     */
-    public RunnableServiceListener(BundleContext bundleContext, TaskScheduler scheduler) {
-        this.bundleContext = bundleContext;
-        this.scheduler = scheduler;
-    }
-
-    @Override
-    public void serviceChanged(ServiceEvent event) {
-        switch (event.getType()) {
-            case ServiceEvent.REGISTERED:
-                scheduleRunnableService(event);
-                break;
-            case ServiceEvent.UNREGISTERING: {
-                unscheduleRunnableService(event);
-            }
-            break;
-            default:
-                break;
-        }
-    }
-
-    /**
-     * Schedules the execution of the Runnable Service of the {@link ServiceEvent}.
-     *
-     * @param event
-     */
-    protected void scheduleRunnableService(ServiceEvent event) {
-        ServiceReference reference = event.getServiceReference();
-        Runnable service = (Runnable) bundleContext.getService(reference);
-        String id = (String) reference.getProperty(KarafTimerTask.ID_PROPERTY);
-        String periodValue = (String) reference.getProperty(KarafTimerTask.PERIOD_PROPERTY);
-
-        if (periodValue != null) {
-            Long period = Long.parseLong(periodValue);
-            KarafTimerTask task = new KarafTimerTask(id, service, period);
-            scheduler.schedule(task);
-        }
-    }
-
-    /**
-     * Unschedules the execution of the Runnable Service of the {@link ServiceEvent}.
-     *
-     * @param event
-     */
-    protected void unscheduleRunnableService(ServiceEvent event) {
-        ServiceReference reference = event.getServiceReference();
-        String id = (String) reference.getProperty(KarafTimerTask.ID_PROPERTY);
-        scheduler.unschedule(id);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/TaskScheduler.java
----------------------------------------------------------------------
diff --git a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/TaskScheduler.java b/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/TaskScheduler.java
deleted file mode 100644
index adddc56..0000000
--- a/scheduler/core/src/main/java/org/apache/karaf/scheduler/core/TaskScheduler.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.karaf.scheduler.core;
-
-import java.util.Map;
-import java.util.Timer;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class TaskScheduler {
-
-    private final Timer timer = new Timer();
-    private final Map<String,KarafTimerTask> tasks = new ConcurrentHashMap<String, KarafTimerTask>();
-
-    /**
-     * Schedule a {@link KarafTimerTask}
-     * @param task
-     */
-    public void schedule(KarafTimerTask task) {
-        if(task != null) {
-            tasks.put(task.getId(),task);
-            timer.schedule(task,0,task.getSchedulePeriod());
-        }
-
-    }
-
-    /**
-     * Unschedule a {@link KarafTimerTask}
-     * @param id The id of the task to unschedule.
-     */
-    public void unschedule(String id) {
-        if(id != null) {
-            KarafTimerTask task = tasks.remove(id);
-            if(task != null) {
-                task.cancel();
-            }
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/core/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/scheduler/core/src/main/resources/OSGI-INF/bundle.info b/scheduler/core/src/main/resources/OSGI-INF/bundle.info
deleted file mode 100644
index a45c2c5..0000000
--- a/scheduler/core/src/main/resources/OSGI-INF/bundle.info
+++ /dev/null
@@ -1,20 +0,0 @@
-h1. Synopsis
-
-${project.name}
-
-${project.description}
-
-Maven URL:
-[mvn:${project.groupId}/${project.artifactId}/${project.version}]
-
-h1. Description
-
-This bundle registers a service listener, which listens from service events related to java.lang.Runnable interface.
-Each service with such interface may be added the following properties:
-
-org.apache.karaf.scheduler.task.id
-org.apache.karaf.scheduler.task.period
-
-If both properties are found, then the Runnable is decorated by a TimerTask and scheduled for repeated execution using the period property.
-
-

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/pom.xml
----------------------------------------------------------------------
diff --git a/scheduler/pom.xml b/scheduler/pom.xml
index 53551b9..6b061dd 100644
--- a/scheduler/pom.xml
+++ b/scheduler/pom.xml
@@ -22,19 +22,98 @@
     <modelVersion>4.0.0</modelVersion>
 
     <parent>
-        <artifactId>karaf</artifactId>
         <groupId>org.apache.karaf</groupId>
+        <artifactId>karaf</artifactId>
         <version>4.0.0-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <groupId>org.apache.karaf</groupId>
-    <artifactId>scheduler</artifactId>
-    <packaging>pom</packaging>
-    <name>Apache Karaf :: Scheduler</name>
+    <groupId>org.apache.karaf.scheduler</groupId>
+    <artifactId>org.apache.karaf.scheduler.core</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Scheduler :: Core</name>
+    <description>This bundle provides a Service Listener which listnes for Runnable Services and schedules their execution, based  on the service properties</description>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*.info</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>org.apache.karaf.scheduler.core.Activator</Bundle-Activator>
+                        <Karaf-Commands>org.apache.karaf.scheduler.command</Karaf-Commands>
+                        <Export-Package>
+                            org.apache.karaf.scheduler;version=${project.version};-noimport:=true
+                        </Export-Package>
+                        <Import-Package>
+                            !com.mchange.*,
+                            !oracle.*,
+                            !org.quartz.*,
+                            !weblogic.*,
+                            !javax.transaction,
+                            *
+                        </Import-Package>
+                        <Private-Package>
+                            org.apache.karaf.scheduler.core,
+                            org.apache.karaf.scheduler.command,
+                            org.apache.karaf.util.tracker,
+                            org.quartz,
+                            org.quartz.core.*,
+                            org.quartz.listeners.*,
+                            org.quartz.impl.*,
+                            org.quartz.spi.*,
+                            org.quartz.simpl.*,
+                            org.quartz.utils.*,
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 
-    <modules>
-        <module>core</module>
-    </modules>
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <version>2.2.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
 
 </project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/Job.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/Job.java b/scheduler/src/main/java/org/apache/karaf/scheduler/Job.java
new file mode 100644
index 0000000..1d10682
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/Job.java
@@ -0,0 +1,34 @@
+/*
+ * 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.karaf.scheduler;
+
+/**
+ * A job is executed by the {@link Scheduler} service.
+ * If the implementation of the job requires certain environment information
+ * it can implement this interface to get additional information
+ * through the provided {@link JobContext}.
+ * If no additional information is required, implementing {@link Runnable} is
+ * sufficient.
+ */
+public interface Job {
+
+    /**
+     * Execute this job.
+     * @param context The context of the job.
+     */
+    void execute(JobContext context);
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/JobContext.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/JobContext.java b/scheduler/src/main/java/org/apache/karaf/scheduler/JobContext.java
new file mode 100644
index 0000000..0660e29
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/JobContext.java
@@ -0,0 +1,38 @@
+/*
+ * 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.karaf.scheduler;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * The context for a {@link Job}.
+ */
+public interface JobContext {
+
+    /**
+     * Get the name of the scheduled job.
+     * @return The name of the job.
+     */
+    String getName();
+
+    /**
+     * Get the configuration provided when the job was scheduled.
+     * @return A non-null map of values.
+     */
+    Map<String, Serializable> getConfiguration();
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/ScheduleOptions.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/ScheduleOptions.java b/scheduler/src/main/java/org/apache/karaf/scheduler/ScheduleOptions.java
new file mode 100644
index 0000000..695f471
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/ScheduleOptions.java
@@ -0,0 +1,59 @@
+/*
+ * 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.karaf.scheduler;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Scheduler options provide an extensible way of defining how to schedule a job.
+ * An option can be created via the scheduler.
+ *
+ * @since 2.3
+ */
+public interface ScheduleOptions {
+
+    /**
+     * Add optional configuration for the job.
+     *
+     * @param config An optional configuration object - this configuration is only passed to the job the job implements {@link Job}.
+     */
+    ScheduleOptions config(final Map<String, Serializable> config);
+
+    /**
+     * Sets the name of the job.
+     * A job only needs a name if it is scheduled and should be cancelled later on. The name can then be used to cancel the job.
+     * If a second job with the same name is started, the second one replaces the first one.
+     *
+     * @param name The job name
+     */
+    ScheduleOptions name(final String name);
+
+    /**
+     * Flag indicating whether the job can be run concurrently.
+     * This defaults to false.
+     *
+     * @param flag Whether this job can run even if previous scheduled runs are still running.
+     */
+    ScheduleOptions canRunConcurrently(final boolean flag);
+
+    String name();
+
+    boolean canRunConcurrently();
+
+    String schedule();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/Scheduler.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/Scheduler.java b/scheduler/src/main/java/org/apache/karaf/scheduler/Scheduler.java
new file mode 100644
index 0000000..e267c28
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/Scheduler.java
@@ -0,0 +1,130 @@
+/*
+ * 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.karaf.scheduler;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.quartz.SchedulerException;
+
+/**
+ * A scheduler to schedule time/cron based jobs.
+ * A job is an object that is executed/fired by the scheduler. The object
+ * should either implement the {@link Job} interface or the {@link Runnable}
+ * interface.
+ *
+ * A job can be scheduled either by creating a {@link ScheduleOptions} instance
+ * through one of the scheduler methods and then calling {@link #schedule(Object, ScheduleOptions)}
+ * or
+ * by using the whiteboard pattern and registering a Runnable service with either
+ * the {@link #PROPERTY_SCHEDULER_EXPRESSION} or {@link #PROPERTY_SCHEDULER_PERIOD}
+ * property. Services registered by the whiteboard pattern can by default run concurrently,
+ * which usually is not wanted. Therefore it is advisable to also set the
+ * {@link #PROPERTY_SCHEDULER_CONCURRENT} property with Boolean.FALSE.
+ */
+public interface Scheduler {
+
+    /**
+     * Name of the configuration property to define the period for a job.
+     * The period is expressed in seconds.
+     * This property needs to be of type Long.
+     */
+    String PROPERTY_SCHEDULER_PERIOD = "scheduler.period";
+
+    /**
+     * Name of the configuration property to define if a periodically job should be scheduled immediate.
+     * Default is to not startup immediate, the job is started the first time after the period has expired.
+     * This property needs to be of type Boolean.
+     */
+    String PROPERTY_SCHEDULER_IMMEDIATE = "scheduler.immediate";
+
+    /** Name of the configuration property to define the cron expression for a job. */
+    String PROPERTY_SCHEDULER_EXPRESSION = "scheduler.expression";
+
+    /** Name of the configuration property to define if the job can be run concurrently. */
+    String PROPERTY_SCHEDULER_CONCURRENT = "scheduler.concurrent";
+
+    /** Name of the configuration property to define the job name. */
+    String PROPERTY_SCHEDULER_NAME = "scheduler.name";
+
+
+    /**
+     * Schedule a job based on the options.
+     *
+     * Note that if a job with the same name has already been added, the old job is cancelled and this new job replaces
+     * the old job.
+     *
+     * The job object needs either to be a {@link Job} or a {@link Runnable}. The options have to be created
+     * by one of the provided methods from this scheduler.
+     *
+     * @param job The job to execute (either {@link Job} or {@link Runnable}).
+     * @param options Required options defining how to schedule the job
+     * @throws SchedulerException if the job can't be scheduled
+     * @throws IllegalArgumentException If the preconditions are not met
+     * @see #NOW()
+     * @see #NOW(int, long)
+     * @see #AT(Date)
+     * @see #AT(Date, int, long)
+     * @see #EXPR(String)
+     */
+    void schedule(Object job, ScheduleOptions options) throws IllegalArgumentException, SchedulerException;
+
+    /**
+     * Remove a scheduled job by name.
+     *
+     * @param jobName The name of the job.
+     * @return <code>true</code> if the job existed and could be stopped, <code>false</code> otherwise.
+     */
+    boolean unschedule(String jobName);
+
+    Map<Object, ScheduleOptions> getJobs() throws SchedulerException;
+
+    /**
+     * Create a schedule options to fire a job immediately and only once.
+     */
+    ScheduleOptions NOW();
+
+    /**
+     * Create a schedule options to fire a job immediately more than once.
+     * @param times The number of times this job should be started (must be higher than 1 or
+     *              -1 for endless)
+     * @param period Every period seconds this job is started (must be at higher than 0).
+     */
+    ScheduleOptions NOW(int times, long period);
+
+    /**
+     * Create a schedule options to fire a job once at a specific date
+     * @param date The date this job should be run.
+     */
+    ScheduleOptions AT(final Date date);
+
+    /**
+     * Create a schedule options to fire a job period starting at a specific date
+     * @param date The date this job should be run.
+     * @param times The number of times this job should be started (must be higher than 1 or
+     *              -1 for endless)
+     * @param period Every period seconds this job is started (must be at higher than 0).
+     */
+    ScheduleOptions AT(final Date date, int times, long period);
+
+    /**
+     * Create a schedule options to schedule the job based on the expression
+     * @param expression The cron exception
+     */
+    ScheduleOptions EXPR(final String expression);
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/command/List.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/command/List.java b/scheduler/src/main/java/org/apache/karaf/scheduler/command/List.java
new file mode 100644
index 0000000..1078bb4
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/command/List.java
@@ -0,0 +1,48 @@
+/*
+ * 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.karaf.scheduler.command;
+
+import java.util.Map;
+
+import org.apache.karaf.scheduler.ScheduleOptions;
+import org.apache.karaf.scheduler.Scheduler;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+@Command(scope = "scheduler", name = "list", description = "List scheduled jobs")
+@Service
+public class List implements Action {
+
+    @Reference
+    Scheduler scheduler;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Name");
+        table.column("Schedule");
+        Map<Object, ScheduleOptions> jobs = scheduler.getJobs();
+        for (Map.Entry<Object, ScheduleOptions> entry : jobs.entrySet()) {
+            table.addRow().addContent(entry.getValue().name(), entry.getValue().schedule());
+        }
+        table.print(System.out);
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/core/Activator.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/core/Activator.java b/scheduler/src/main/java/org/apache/karaf/scheduler/core/Activator.java
new file mode 100644
index 0000000..925d35b
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/core/Activator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.karaf.scheduler.core;
+
+import org.apache.karaf.scheduler.Scheduler;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.quartz.simpl.SimpleThreadPool;
+import org.quartz.spi.ThreadPool;
+
+public class Activator extends BaseActivator {
+
+    private ThreadPool threadPool;
+    private QuartzScheduler scheduler;
+    private WhiteboardHandler whiteboardHandler;
+
+    @Override
+    protected void doStart() throws Exception {
+        threadPool = new SimpleThreadPool(4, Thread.NORM_PRIORITY);
+        scheduler = new QuartzScheduler(threadPool);
+        whiteboardHandler = new WhiteboardHandler(bundleContext, scheduler);
+        register(Scheduler.class, scheduler);
+    }
+
+    @Override
+    protected void doStop() {
+        if (whiteboardHandler != null) {
+            whiteboardHandler.deactivate();
+            whiteboardHandler = null;
+        }
+        if (scheduler != null) {
+            scheduler.deactivate();
+            scheduler = null;
+        }
+        super.doStop();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/core/InternalScheduleOptions.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/core/InternalScheduleOptions.java b/scheduler/src/main/java/org/apache/karaf/scheduler/core/InternalScheduleOptions.java
new file mode 100644
index 0000000..6651ea8
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/core/InternalScheduleOptions.java
@@ -0,0 +1,160 @@
+/*
+ * 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.karaf.scheduler.core;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.karaf.scheduler.ScheduleOptions;
+import org.quartz.CronExpression;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.SimpleScheduleBuilder;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+
+/**
+ * Scheduler options provide an extensible way of defining how to schedule a job.
+ * @since 2.3
+ */
+public class InternalScheduleOptions implements ScheduleOptions {
+
+    public String name;
+
+    public boolean canRunConcurrently = false;
+
+    public Map<String, Serializable> configuration;
+
+    public final String schedule;
+
+    public final TriggerBuilder<? extends Trigger> trigger;
+
+    public final IllegalArgumentException argumentException;
+
+    public InternalScheduleOptions(Date date) {
+        if ( date == null ) {
+            this.trigger = null;
+            this.argumentException = new IllegalArgumentException("Date can't be null");
+        } else {
+            this.trigger = TriggerBuilder.newTrigger().startAt(date);
+            this.argumentException = null;
+        }
+        this.schedule = "at(" + formatDate(date) + ")";
+    }
+
+    public InternalScheduleOptions(Date date, int times, long period) {
+        TriggerBuilder<? extends Trigger> trigger = null;
+        IllegalArgumentException argumentException = null;
+        try {
+            if (date == null) {
+                throw new IllegalArgumentException("Date can't be null");
+            }
+            if (times < 2 && times != -1) {
+                throw new IllegalArgumentException("Times argument must be higher than 1 or -1");
+            }
+            if (period < 1) {
+                throw new IllegalArgumentException("Period argument must be higher than 0");
+            }
+            final SimpleScheduleBuilder sb;
+            if (times == -1) {
+                sb = SimpleScheduleBuilder.simpleSchedule().repeatForever();
+            } else {
+                sb = SimpleScheduleBuilder.simpleSchedule().withRepeatCount(times - 1);
+            }
+            trigger = TriggerBuilder.newTrigger()
+                        .startAt(date)
+                        .withSchedule(sb.withIntervalInMilliseconds(period * 1000));
+        } catch (IllegalArgumentException e) {
+            argumentException = e;
+        }
+        this.trigger = trigger;
+        this.argumentException = argumentException;
+        this.schedule = "at(" + formatDate(date) + ", " + times + ", " + period + ")";
+    }
+
+    public InternalScheduleOptions(String expression) {
+        TriggerBuilder<? extends Trigger> trigger = null;
+        IllegalArgumentException argumentException = null;
+        try {
+            if ( expression == null ) {
+                throw new IllegalArgumentException("Expression can't be null");
+            }
+            if ( !CronExpression.isValidExpression(expression) ) {
+                throw new IllegalArgumentException("Expression is invalid : " + expression);
+            }
+            trigger = TriggerBuilder.newTrigger()
+                            .withSchedule(CronScheduleBuilder.cronSchedule(expression));
+        } catch (IllegalArgumentException e) {
+            argumentException = e;
+        }
+        this.trigger = trigger;
+        this.argumentException = argumentException;
+        this.schedule = "cron(" + expression + ")";
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.ScheduleOptions#config(java.util.Map)
+     */
+    public ScheduleOptions config(final  Map<String, Serializable> config) {
+        this.configuration = config;
+        return this;
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.ScheduleOptions#name(java.lang.String)
+     */
+    public ScheduleOptions name(final String name) {
+        this.name = name;
+        return this;
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.ScheduleOptions#canRunConcurrently(boolean)
+     */
+    public ScheduleOptions canRunConcurrently(final boolean flag) {
+        this.canRunConcurrently = flag;
+        return this;
+    }
+
+    @Override
+    public String name() {
+        return this.name;
+    }
+
+    @Override
+    public boolean canRunConcurrently() {
+        return this.canRunConcurrently;
+    }
+
+    @Override
+    public String schedule() {
+        return schedule;
+    }
+
+    private String formatDate(Date date) {
+        if (date == null) {
+            return "null";
+        }
+        TimeZone tz = TimeZone.getTimeZone("UTC");
+        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
+        df.setTimeZone(tz);
+        return df.format(date);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/core/NonParallelQuartzJobExecutor.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/core/NonParallelQuartzJobExecutor.java b/scheduler/src/main/java/org/apache/karaf/scheduler/core/NonParallelQuartzJobExecutor.java
new file mode 100644
index 0000000..ec22506
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/core/NonParallelQuartzJobExecutor.java
@@ -0,0 +1,31 @@
+/*
+ * 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.karaf.scheduler.core;
+
+import org.quartz.DisallowConcurrentExecution;
+
+
+/**
+ * This component is responsible to launch a {@link org.apache.karaf.scheduler.Job}
+ * or {@link Runnable} in a Quartz Scheduler but non concurrently.
+ *
+ */
+@DisallowConcurrentExecution
+public class NonParallelQuartzJobExecutor extends QuartzJobExecutor {
+
+    // nothing to code here
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzJobExecutor.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzJobExecutor.java b/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzJobExecutor.java
new file mode 100644
index 0000000..9a07c1f
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzJobExecutor.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.karaf.scheduler.core;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.apache.karaf.scheduler.JobContext;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+
+/**
+ * This component is responsible to launch a {@link org.apache.karaf.scheduler.Job}
+ * or {@link Runnable} in a Quartz Scheduler.
+ *
+ */
+public class QuartzJobExecutor implements Job {
+
+    /**
+     * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
+     */
+    public void execute(final JobExecutionContext context) throws JobExecutionException {
+
+        final JobDataMap data = context.getJobDetail().getJobDataMap();
+        final Object job = data.get(QuartzScheduler.DATA_MAP_OBJECT);
+        final Logger logger = (Logger)data.get(QuartzScheduler.DATA_MAP_LOGGER);
+
+        try {
+            logger.debug("Executing job {} with name {}", job, data.get(QuartzScheduler.DATA_MAP_NAME));
+            if (job instanceof org.apache.karaf.scheduler.Job) {
+                @SuppressWarnings("unchecked")
+                final InternalScheduleOptions options = (InternalScheduleOptions) data.get(QuartzScheduler.DATA_MAP_OPTIONS);
+                final String name = (String) data.get(QuartzScheduler.DATA_MAP_NAME);
+
+                final JobContext jobCtx = new JobContextImpl(name, options.configuration);
+                ((org.apache.karaf.scheduler.Job) job).execute(jobCtx);
+            } else if (job instanceof Runnable) {
+                ((Runnable) job).run();
+            } else {
+                logger.error("Scheduled job {} is neither a job nor a runnable.", job);
+            }
+        } catch (final Throwable t) {
+            // there is nothing we can do here, so we just log
+            logger.error("Exception during job execution of " + job + " : " + t.getMessage(), t);
+        }
+    }
+
+    public static final class JobContextImpl implements JobContext {
+
+        protected final Map<String, Serializable> configuration;
+        protected final String name;
+
+        public JobContextImpl(String name, Map<String, Serializable> config) {
+            this.name = name;
+            this.configuration = config;
+        }
+
+        /**
+         * @see org.apache.karaf.scheduler.JobContext#getConfiguration()
+         */
+        public Map<String, Serializable> getConfiguration() {
+            return this.configuration;
+        }
+
+        /**
+         * @see org.apache.karaf.scheduler.JobContext#getName()
+         */
+        public String getName() {
+            return this.name;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzScheduler.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzScheduler.java b/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzScheduler.java
new file mode 100644
index 0000000..a25d4e4
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/core/QuartzScheduler.java
@@ -0,0 +1,294 @@
+/*
+ * 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.karaf.scheduler.core;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.karaf.scheduler.Job;
+import org.apache.karaf.scheduler.ScheduleOptions;
+import org.apache.karaf.scheduler.Scheduler;
+import org.quartz.JobBuilder;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.impl.DirectSchedulerFactory;
+import org.quartz.impl.matchers.GroupMatcher;
+import org.quartz.simpl.RAMJobStore;
+import org.quartz.spi.ThreadPool;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The quartz based implementation of the scheduler.
+ *
+ */
+public class QuartzScheduler implements Scheduler {
+
+    /** Default logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private static final String PREFIX = "Apache Karaf Quartz Scheduler ";
+
+    private static final String QUARTZ_SCHEDULER_NAME = "ApacheKaraf";
+
+    /** Map key for the job object */
+    static final String DATA_MAP_OBJECT = "QuartzJobScheduler.Object";
+
+    /** Map key for the job name */
+    static final String DATA_MAP_NAME = "QuartzJobScheduler.JobName";
+
+    /** Map key for the scheduling options. */
+    static final String DATA_MAP_OPTIONS = "QuartzJobScheduler.Options";
+
+    /** Map key for the logger. */
+    static final String DATA_MAP_LOGGER = "QuartzJobScheduler.Logger";
+
+    /** The quartz scheduler. */
+    private volatile org.quartz.Scheduler scheduler;
+
+    public QuartzScheduler(ThreadPool threadPool) throws SchedulerException {
+        // SLING-2261 Prevent Quartz from checking for updates
+        System.setProperty("org.terracotta.quartz.skipUpdateCheck", Boolean.TRUE.toString());
+
+        final DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
+        // unique run id
+        final String runID = new Date().toString().replace(' ', '_');
+        factory.createScheduler(QUARTZ_SCHEDULER_NAME, runID, threadPool, new RAMJobStore());
+        // quartz does not provide a way to get the scheduler by name AND runID, so we have to iterate!
+        final Iterator<org.quartz.Scheduler> allSchedulersIter = factory.getAllSchedulers().iterator();
+        while ( scheduler == null && allSchedulersIter.hasNext() ) {
+            final org.quartz.Scheduler current = allSchedulersIter.next();
+            if ( QUARTZ_SCHEDULER_NAME.equals(current.getSchedulerName())
+                    && runID.equals(current.getSchedulerInstanceId()) ) {
+                scheduler = current;
+            }
+        }
+        if ( scheduler == null ) {
+            throw new SchedulerException("Unable to find new scheduler with name " + QUARTZ_SCHEDULER_NAME + " and run ID " + runID);
+        }
+
+        scheduler.start();
+        if ( this.logger.isDebugEnabled() ) {
+            this.logger.debug(PREFIX + "started.");
+        }
+    }
+
+    /**
+     * Deactivate this component.
+     * Stop the scheduler.
+     */
+    public void deactivate() {
+        final org.quartz.Scheduler s = this.scheduler;
+        this.scheduler = null;
+        this.dispose(s);
+    }
+
+    /**
+     * Dispose the quartz scheduler
+     * @param s The scheduler.
+     */
+    private void dispose(final org.quartz.Scheduler s) {
+        if ( s != null ) {
+            try {
+                s.shutdown();
+            } catch (SchedulerException e) {
+                this.logger.debug("Exception during shutdown of scheduler.", e);
+            }
+            if ( this.logger.isDebugEnabled() ) {
+                this.logger.debug(PREFIX + "stopped.");
+            }
+        }
+    }
+
+    /**
+     * Initialize the data map for the job executor.
+     */
+    private JobDataMap initDataMap(final String  jobName,
+                                   final Object  job,
+                                   final InternalScheduleOptions options) {
+        final JobDataMap jobDataMap = new JobDataMap();
+
+        jobDataMap.put(DATA_MAP_OBJECT, job);
+
+        jobDataMap.put(DATA_MAP_NAME, jobName);
+        jobDataMap.put(DATA_MAP_LOGGER, this.logger);
+        jobDataMap.put(DATA_MAP_OPTIONS, options);
+
+        return jobDataMap;
+    }
+
+    /**
+     * Create the job detail.
+     */
+    private JobDetail createJobDetail(final String name,
+                                      final JobDataMap jobDataMap,
+                                      final boolean concurrent) {
+        return JobBuilder.newJob((concurrent ? QuartzJobExecutor.class : NonParallelQuartzJobExecutor.class))
+                .withIdentity(name)
+                .usingJobData(jobDataMap)
+                .build();
+    }
+
+    /**
+     * Check the job object, either runnable or job is allowed
+     */
+    private void checkJob(final Object job)
+            throws IllegalArgumentException {
+        if (!(job instanceof Runnable) && !(job instanceof Job)) {
+            throw new IllegalArgumentException("Job object is neither an instance of " + Runnable.class.getName() + " nor " + Job.class.getName());
+        }
+    }
+
+    /** Used by the web console plugin. */
+    org.quartz.Scheduler getScheduler() {
+        return this.scheduler;
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#NOW()
+     */
+    public ScheduleOptions NOW() {
+        return AT(new Date());
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#NOW(int, long)
+     */
+    public ScheduleOptions NOW(int times, long period) {
+        return AT(new Date(), times, period);
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#AT(java.util.Date)
+     */
+    public ScheduleOptions AT(Date date) {
+        return new InternalScheduleOptions(date);
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#AT(java.util.Date, int, long)
+     */
+    public ScheduleOptions AT(Date date, int times, long period) {
+        return new InternalScheduleOptions(date, times, period);
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#EXPR(java.lang.String)
+     */
+    public ScheduleOptions EXPR(String expression) {
+        return new InternalScheduleOptions(expression);
+    }
+
+    /**
+     * Schedule a job
+     * @see org.apache.karaf.scheduler.Scheduler#schedule(java.lang.Object, org.apache.karaf.scheduler.ScheduleOptions)
+     * @throws SchedulerException if the job can't be scheduled
+     * @throws IllegalArgumentException If the preconditions are not met
+     */
+    public void schedule(final Object job, final ScheduleOptions options) throws IllegalArgumentException, SchedulerException {
+        this.checkJob(job);
+
+        if ( !(options instanceof InternalScheduleOptions)) {
+            throw new IllegalArgumentException("Options has not been created via schedule or is null.");
+        }
+        final InternalScheduleOptions opts = (InternalScheduleOptions)options;
+
+        if ( opts.argumentException != null ) {
+            throw opts.argumentException;
+        }
+
+        // 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.scheduler;
+        if ( s == null ) {
+            throw new IllegalStateException("Scheduler is not available anymore.");
+        }
+
+        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
+            }
+            name = opts.name;
+        } else {
+            name = job.getClass().getName() + ':' + UUID.randomUUID();
+        }
+
+        final Trigger trigger = opts.trigger.withIdentity(name).build();
+
+        // create the data map
+        final JobDataMap jobDataMap = this.initDataMap(name, job, opts);
+
+        final JobDetail detail = this.createJobDetail(name, jobDataMap, opts.canRunConcurrently);
+
+        this.logger.debug("Scheduling job {} with name {} and trigger {}", job, name, trigger);
+        s.scheduleJob(detail, trigger);
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#unschedule(java.lang.String)
+     */
+    public boolean unschedule(final String jobName) {
+        final org.quartz.Scheduler s = this.scheduler;
+        if ( jobName != null && s != null ) {
+            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;
+                }
+            } catch (final SchedulerException ignored) {
+                // ignore
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Map<Object, ScheduleOptions> getJobs() throws SchedulerException {
+        Map<Object, ScheduleOptions> jobs = new HashMap<>();
+        org.quartz.Scheduler s = this.scheduler;
+        if (s != null) {
+            for (String group : s.getJobGroupNames()) {
+                for (JobKey key : s.getJobKeys(GroupMatcher.jobGroupEquals(group))) {
+                    JobDetail detail = s.getJobDetail(key);
+                    ScheduleOptions options = (ScheduleOptions) detail.getJobDataMap().get(DATA_MAP_OPTIONS);
+                    Object job = detail.getJobDataMap().get(DATA_MAP_OBJECT);
+                    jobs.put(job, options);
+                }
+            }
+        }
+        return jobs;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/java/org/apache/karaf/scheduler/core/WhiteboardHandler.java
----------------------------------------------------------------------
diff --git a/scheduler/src/main/java/org/apache/karaf/scheduler/core/WhiteboardHandler.java b/scheduler/src/main/java/org/apache/karaf/scheduler/core/WhiteboardHandler.java
new file mode 100644
index 0000000..2d0a9df
--- /dev/null
+++ b/scheduler/src/main/java/org/apache/karaf/scheduler/core/WhiteboardHandler.java
@@ -0,0 +1,143 @@
+/*
+ * 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.karaf.scheduler.core;
+
+import java.util.Date;
+
+import org.apache.karaf.scheduler.Job;
+import org.apache.karaf.scheduler.Scheduler;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The quartz based implementation of the scheduler.
+ *
+ */
+public class WhiteboardHandler {
+
+    /** Default logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private Scheduler scheduler;
+
+    private ServiceTracker<?,?> serviceTracker;
+
+    public WhiteboardHandler(final BundleContext context, Scheduler scheduler) throws InvalidSyntaxException {
+        this.scheduler = scheduler;
+        this.serviceTracker = new ServiceTracker<>(context,
+                context.createFilter("(|(" + Constants.OBJECTCLASS + "=" + Runnable.class.getName() + ")" +
+                        "(" + Constants.OBJECTCLASS + "=" + Job.class.getName() + "))"),
+                new ServiceTrackerCustomizer<Object,Object>() {
+
+                    public synchronized void  removedService(final ServiceReference reference, final Object service) {
+                        context.ungetService(reference);
+                        unregister(reference, service);
+                    }
+
+                    public synchronized void modifiedService(final ServiceReference reference, final Object service) {
+                        unregister(reference, service);
+                        register(reference, service);
+                    }
+
+                    public synchronized Object addingService(final ServiceReference reference) {
+                        final Object obj = context.getService(reference);
+                        if ( obj != null ) {
+                            register(reference, obj);
+                        }
+                        return obj;
+                    }
+                });
+        this.serviceTracker.open();
+    }
+
+    /**
+     * Deactivate this component.
+     */
+    public void deactivate() {
+        this.serviceTracker.close();
+    }
+
+
+    /**
+     * Create unique identifier
+     */
+    private String getServiceIdentifier(final ServiceReference ref) {
+        String name = (String)ref.getProperty(Scheduler.PROPERTY_SCHEDULER_NAME);
+        if ( name == null ) {
+            name = (String)ref.getProperty(Constants.SERVICE_PID);
+            if ( name == null ) {
+                name = "Registered Service";
+            }
+        }
+        // now append service id to create a unique identifier
+        name = name + "." + ref.getProperty(Constants.SERVICE_ID);
+        return name;
+    }
+
+    /**
+     * Register a job or task
+     */
+    private void register(final ServiceReference ref, final Object job) {
+        final String name = getServiceIdentifier(ref);
+        final Boolean concurrent = (Boolean) ref.getProperty(Scheduler.PROPERTY_SCHEDULER_CONCURRENT);
+        final String expression = (String) ref.getProperty(Scheduler.PROPERTY_SCHEDULER_EXPRESSION);
+        try {
+            if (expression != null) {
+                this.scheduler.schedule(job, this.scheduler.EXPR(expression)
+                        .name(name)
+                        .canRunConcurrently((concurrent != null ? concurrent : true)));
+            } else {
+                final Long period = (Long) ref.getProperty(Scheduler.PROPERTY_SCHEDULER_PERIOD);
+                if (period != null) {
+                    if (period < 1) {
+                        this.logger.debug("Ignoring service {} : scheduler period is less than 1.", ref);
+                    } else {
+                        boolean immediate = false;
+                        if (ref.getProperty(Scheduler.PROPERTY_SCHEDULER_IMMEDIATE) != null) {
+                            immediate = (Boolean) ref.getProperty(Scheduler.PROPERTY_SCHEDULER_IMMEDIATE);
+                        }
+                        final Date date = new Date();
+                        if (!immediate) {
+                            date.setTime(System.currentTimeMillis() + period * 1000);
+                        }
+                        this.scheduler.schedule(job, this.scheduler.AT(date, -1, period)
+                                .name(name)
+                                .canRunConcurrently((concurrent != null ? concurrent : true)));
+                    }
+                } else {
+                    this.logger.debug("Ignoring service {} : no scheduling property found.", ref);
+                }
+            }
+        } catch (Exception e) {
+            logger.warn("Error scheduling job", e);
+        }
+    }
+
+    /**
+     * Unregister a service.
+     */
+    private void unregister(final ServiceReference reference, final Object service) {
+        final String name = getServiceIdentifier(reference);
+        this.scheduler.unschedule(name);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/scheduler/src/main/resources/OSGI-INF/bundle.info
----------------------------------------------------------------------
diff --git a/scheduler/src/main/resources/OSGI-INF/bundle.info b/scheduler/src/main/resources/OSGI-INF/bundle.info
new file mode 100644
index 0000000..074eef4
--- /dev/null
+++ b/scheduler/src/main/resources/OSGI-INF/bundle.info
@@ -0,0 +1,23 @@
+h1. Synopsis
+
+${project.name}
+
+${project.description}
+
+Maven URL:
+[mvn:${project.groupId}/${project.artifactId}/${project.version}]
+
+h1. Description
+
+This bundle registers a service listener, which listens from service events related to java.lang.Runnable and org.apache.karaf.scheduler.Job interface.
+Each service with such interface may be added the following properties:
+
+scheduler.name
+scheduler.period
+scheduler.expression
+scheduler.immediate
+scheduler.concurrent
+
+One of scheduler.period or scheduler.expression is mandatory.
+
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/eb61abc8/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java b/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
index 4f34b06..0948f0b 100644
--- a/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
+++ b/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
@@ -70,6 +70,7 @@ public class BaseActivator implements BundleActivator, SingleServiceTracker.Sing
             try {
                 doStart();
             } catch (Exception e) {
+                logger.warn("Error starting activator", e);
                 doStop();
             }
         } else {