You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by gh...@apache.org on 2020/07/03 05:46:27 UTC

[felix-dev] 01/03: FELIX-6265 Remove boilerplate classes, allow to specify 7th field year at least as '*', use thread pool for cron scheduler itself as well

This is an automated email from the ASF dual-hosted git repository.

ghenzler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git

commit da6061d2d76b5926b77b82a65b8d344b30b66346
Author: georg.henzler <ge...@netcentric.biz>
AuthorDate: Thu Jul 2 17:35:50 2020 +0200

    FELIX-6265 Remove boilerplate classes, allow to specify 7th field year
    at least as '*', use thread pool for cron scheduler itself as well
---
 .../executor/HealthCheckExecutorThreadPool.java    |   4 +-
 .../hc/core/impl/executor/HealthCheckFuture.java   |  36 +-
 .../executor/async/AsyncHealthCheckExecutor.java   |  10 +-
 .../hc/core/impl/monitor/HealthCheckMonitor.java   |   4 +-
 ...lerProviderEnabler.java => CronJobFactory.java} |  67 +-
 .../scheduling/cron/HealthCheckCronScheduler.java  |  30 -
 .../cron/embedded/AsyncEmbeddedCronJob.java        |  99 ++-
 .../scheduling/cron/embedded/EmbeddedCronJob.java  |  28 -
 .../cron/embedded/EmbeddedCronParser.java          |  13 +-
 .../cron/embedded/EmbeddedCronScheduler.java       | 154 +---
 .../embedded/EmbeddedCronSchedulerProvider.java    |  54 +-
 .../cron/embedded/EmbeddedCronSchedulerTask.java   |  67 --
 .../scheduling/cron/quartz/AsyncQuartzCronJob.java |  22 +-
 .../scheduling/cron/quartz/CronJobFactory.java     |  47 --
 .../cron/quartz/QuartzCronScheduler.java           |   1 +
 .../cron/quartz/QuartzCronSchedulerProvider.java   |  33 +-
 .../cron/embedded/EmbeddedCronParserTest.java      | 777 ++++++++++++++++++++-
 .../cron/embedded/EmbeddedCronSchedulerTest.java   | 192 ++---
 .../cron/embedded/EmbeddedCronTriggerTest.java     | 718 -------------------
 .../cron/embedded/EmbeddedSampleCronJob1.java      |  60 --
 .../cron/embedded/EmbeddedSampleCronJob2.java      |  29 -
 21 files changed, 1092 insertions(+), 1353 deletions(-)

diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java
index f5c65c0..aa4ddb7 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckExecutorThreadPool.java
@@ -17,8 +17,8 @@
  */
 package org.apache.felix.hc.core.impl.executor;
 
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.ThreadFactory;
@@ -104,7 +104,7 @@ public class HealthCheckExecutorThreadPool {
         }
     }
 
-    public ExecutorService getExecutor() {
+    public ScheduledExecutorService getExecutor() {
         return executor;
     }
 
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java
index d86409a..c5083b2 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/HealthCheckFuture.java
@@ -20,6 +20,7 @@ package org.apache.felix.hc.core.impl.executor;
 import static org.apache.felix.hc.api.FormattingResultLog.msHumanReadable;
 
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Date;
 import java.util.List;
@@ -126,25 +127,38 @@ public class HealthCheckFuture extends FutureTask<ExecutionResult> {
             Class<?> hcClass = legacyHealthCheck.getClass();
             log.debug("Running legacy HC {}, please convert to new interface org.apache.felix.hc.api.HealthCheck!",
                     hcClass.getName());
+            
+            Object result;
             try {
                 Method executeMethod = hcClass.getMethod("execute");
-                Object result = executeMethod.invoke(legacyHealthCheck);
+                result = executeMethod.invoke(legacyHealthCheck);
+            } catch (InvocationTargetException e) {
+                log.healthCheckError("Exception during execute() of Sling HC {}: {}", hcClass.getName(), String.valueOf(e.getTargetException()), e);
+                return new Result(log);
+            } catch (ReflectiveOperationException e) {
+                log.healthCheckError("Could not call Sling HC {} from Felix Runtime: {}", hcClass.getName(), String.valueOf(e), e);
+                return new Result(log);
+            }
+            
+            try {
                 Object resultLog = readPrivateField(result, "resultLog");
 
                 List<?> entries = (List) readPrivateField(resultLog, "entries");
-                for (Object object : entries) {
-                    String statusLegacy = String.valueOf(readPrivateField(object, "status"));
-                    String message = (String) readPrivateField(object, "message");
-                    Exception exception = (Exception) readPrivateField(object, "exception");
-                    if(statusLegacy.equals("DEBUG")) {
-                        log.add(new ResultLog.Entry(message, true, exception));
-                    } else {
-                        statusLegacy = statusLegacy.replace("INFO", Result.Status.OK.name());
-                        log.add(new ResultLog.Entry(Result.Status.valueOf(statusLegacy), message, exception));
+                if(entries != null) {
+                    for (Object object : entries) {
+                        String statusLegacy = String.valueOf(readPrivateField(object, "status"));
+                        String message = (String) readPrivateField(object, "message");
+                        Exception exception = (Exception) readPrivateField(object, "exception");
+                        if(statusLegacy.equals("DEBUG")) {
+                            log.add(new ResultLog.Entry(message, true, exception));
+                        } else {
+                            statusLegacy = statusLegacy.replace("INFO", Result.Status.OK.name());
+                            log.add(new ResultLog.Entry(Result.Status.valueOf(statusLegacy), message, exception));
+                        }
                     }
                 }
             } catch (ReflectiveOperationException e) {
-                log.healthCheckError("Could call and convert Sling HC {} for Felix Runtime", hcClass.getName());
+                log.healthCheckError("Could convert Sling HC result of {} for Felix Runtime: {}", hcClass.getName(), String.valueOf(e), e);
             }
             return new Result(log);
         }
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java
index 70905e3..97df0f6 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/executor/async/AsyncHealthCheckExecutor.java
@@ -39,7 +39,7 @@ import org.apache.felix.hc.core.impl.executor.HealthCheckFuture.Callback;
 import org.apache.felix.hc.core.impl.executor.HealthCheckResultCache;
 import org.apache.felix.hc.core.impl.scheduling.AsyncIntervalJob;
 import org.apache.felix.hc.core.impl.scheduling.AsyncJob;
-import org.apache.felix.hc.core.impl.scheduling.cron.quartz.CronJobFactory;
+import org.apache.felix.hc.core.impl.scheduling.CronJobFactory;
 import org.apache.felix.hc.core.impl.util.HealthCheckFilter;
 import org.apache.felix.hc.core.impl.util.lang.StringUtils;
 import org.osgi.framework.BundleContext;
@@ -74,6 +74,7 @@ public class AsyncHealthCheckExecutor implements ServiceListener {
 
     @Activate
     protected final void activate(final BundleContext bundleContext) throws InvalidSyntaxException {
+        this.bundleContext = bundleContext;
         this.bundleContext.addServiceListener(this, "(objectclass=" + HealthCheck.class.getName() + ")");
         int count = 0;
         HealthCheckFilter healthCheckFilter = new HealthCheckFilter(bundleContext);
@@ -131,7 +132,7 @@ public class AsyncHealthCheckExecutor implements ServiceListener {
         try {
             AsyncJob healthCheckAsyncJob = null;
             if (isAsyncCron(descriptor)) {
-                healthCheckAsyncJob = cronJobFactory.createNewJob(getAsyncJob(descriptor), "job-hc-" + descriptor.getServiceId(), "async-healthchecks", descriptor.getAsyncCronExpression());
+                healthCheckAsyncJob = cronJobFactory.createAsyncCronJob(getAsyncJob(descriptor), "job-hc-" + descriptor.getTitle().replaceAll("\\s+","-"), "async-healthchecks", descriptor.getAsyncCronExpression());
             } else if (isAsyncInterval(descriptor)) {
                 healthCheckAsyncJob = new AsyncIntervalJob(getAsyncJob(descriptor), healthCheckExecutorThreadPool, descriptor.getAsyncIntervalInSec());
             }
@@ -142,8 +143,9 @@ public class AsyncHealthCheckExecutor implements ServiceListener {
             } else {
                 return false;
             }
+            
         } catch (Exception e) {
-            LOG.warn("Could not schedule job for " + descriptor + ". Exception: " + e, e);
+            LOG.warn("Could not schedule async health check for " + descriptor + ". Exception: " + e, e);
             return false;
         }
     }
@@ -227,7 +229,7 @@ public class AsyncHealthCheckExecutor implements ServiceListener {
             } else {
                 result = new ExecutionResult(healthCheckMetadata,
                         new Result(Result.Status.WARN, "Async Health Check with cron expression '" + healthCheckMetadata.getAsyncCronExpression() + 
-                                "' is never executed because quartz bundle is missing."), 0L);
+                                "' is never executed because of misconfiguration."), 0L);
             }
 
         } else {
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java
index 0b28bc7..be2dc59 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/monitor/HealthCheckMonitor.java
@@ -38,7 +38,7 @@ import org.apache.felix.hc.core.impl.executor.CombinedExecutionResult;
 import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
 import org.apache.felix.hc.core.impl.scheduling.AsyncIntervalJob;
 import org.apache.felix.hc.core.impl.scheduling.AsyncJob;
-import org.apache.felix.hc.core.impl.scheduling.cron.quartz.CronJobFactory;
+import org.apache.felix.hc.core.impl.scheduling.CronJobFactory;
 import org.apache.felix.hc.core.impl.util.lang.StringUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
@@ -171,7 +171,7 @@ public class HealthCheckMonitor implements Runnable {
         this.intervalInSec = config.intervalInSec();
         this.cronExpression = config.cronExpression();
         if (StringUtils.isNotBlank(cronExpression)) {
-            monitorJob = cronJobFactory.createNewJob(this,
+            monitorJob = cronJobFactory.createAsyncCronJob(this,
                     "job-hc-monitor-" + componentContext.getProperties().get(ComponentConstants.COMPONENT_ID),
                     "healthcheck-monitor", cronExpression);
         } else if (intervalInSec > 0) {
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/CronSchedulerProviderEnabler.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/CronJobFactory.java
similarity index 50%
rename from healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/CronSchedulerProviderEnabler.java
rename to healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/CronJobFactory.java
index 6518726..cb42bc7 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/CronSchedulerProviderEnabler.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/CronJobFactory.java
@@ -15,67 +15,58 @@
  * KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations under the License.
  */
-package org.apache.felix.hc.core.impl.scheduling.cron;
+package org.apache.felix.hc.core.impl.scheduling;
 
-import static org.apache.felix.hc.core.impl.scheduling.cron.CronSchedulerProviderEnabler.CronType.EMBEDDED;
-import static org.apache.felix.hc.core.impl.scheduling.cron.CronSchedulerProviderEnabler.CronType.QUARTZ;
-import static org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler.CRON_TYPE_PROPERTY;
 import static org.osgi.framework.namespace.PackageNamespace.PACKAGE_NAMESPACE;
 
-import java.util.Collection;
-
+import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
+import org.apache.felix.hc.core.impl.scheduling.cron.embedded.AsyncEmbeddedCronJob;
+import org.apache.felix.hc.core.impl.scheduling.cron.embedded.EmbeddedCronSchedulerProvider;
+import org.apache.felix.hc.core.impl.scheduling.cron.quartz.AsyncQuartzCronJob;
+import org.apache.felix.hc.core.impl.scheduling.cron.quartz.QuartzCronSchedulerProvider;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.wiring.BundleWire;
 import org.osgi.framework.wiring.BundleWiring;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.runtime.ServiceComponentRuntime;
-import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-@Component
-public final class CronSchedulerProviderEnabler {
-
-    enum CronType {
-        QUARTZ, EMBEDDED
-    }
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+/** Component without direct quartz imports (can always start) that will provide a QuartzCronScheduler on demand. */
+@Component(service = CronJobFactory.class)
+public class CronJobFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(CronJobFactory.class);
 
     private static final String QUARTZ_PACKAGE_PREFIX = "org.quartz";
-
+    
     @Reference
-    private ServiceComponentRuntime scr;
+    private HealthCheckExecutorThreadPool healthCheckExecutorThreadPool;
 
-    private BundleContext bundleContext;
+    @Reference
+    private QuartzCronSchedulerProvider quartzCronSchedulerProvider;
+    
+    @Reference
+    private EmbeddedCronSchedulerProvider embeddedCronSchedulerProvider;
+    
+    boolean isQuartzAvailable;
 
     @Activate
-    void activate(final BundleContext bundleContext) {
-        this.bundleContext = bundleContext;
-        if (isQuartzPackageImported()) {
-            enableCronSchedulerProvider(QUARTZ);
-            logger.info("Quartz cron scheduler enabled");
-        } else {
-            enableCronSchedulerProvider(EMBEDDED);
-            logger.info("Embedded cron scheduler enabled");
-        }
+    protected synchronized void activate(BundleContext bundleContext) {
+        isQuartzAvailable = isQuartzAvailable(bundleContext);
+        LOG.info("Quartz is " + (isQuartzAvailable?"":"NOT ")+"available for scheduling cron jobs");
     }
 
-    private void enableCronSchedulerProvider(final CronType cronType) {
-        final Collection<ComponentDescriptionDTO> dtos = scr.getComponentDescriptionDTOs(bundleContext.getBundle());
-        for (final ComponentDescriptionDTO dto : dtos) {
-            if (dto.properties.containsKey(CRON_TYPE_PROPERTY)) {
-                final String type = (String) dto.properties.get(CRON_TYPE_PROPERTY);
-                if (type.equalsIgnoreCase(cronType.name())) {
-                    scr.enableComponent(dto);
-                }
-            }
+    public AsyncJob createAsyncCronJob(Runnable runnable, String id, String group, String cronExpression) {
+
+        if(isQuartzAvailable) {
+            return new AsyncQuartzCronJob(runnable, quartzCronSchedulerProvider, id, group, cronExpression);
+        } else {
+            return new AsyncEmbeddedCronJob(runnable, embeddedCronSchedulerProvider, id, cronExpression);
         }
     }
 
-    private boolean isQuartzPackageImported() {
+    private boolean isQuartzAvailable(BundleContext bundleContext) {
         final BundleWiring wiring = bundleContext.getBundle().adapt(BundleWiring.class);
         for (final BundleWire wire : wiring.getRequiredWires(PACKAGE_NAMESPACE)) {
             final String pkg = (String) wire.getCapability().getAttributes().get(PACKAGE_NAMESPACE);
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/HealthCheckCronScheduler.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/HealthCheckCronScheduler.java
deleted file mode 100644
index b4f6131..0000000
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/HealthCheckCronScheduler.java
+++ /dev/null
@@ -1,30 +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 SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.felix.hc.core.impl.scheduling.cron;
-
-/**
- * A common interface for multiple implementations of cron providers
- */
-@FunctionalInterface
-public interface HealthCheckCronScheduler {
-
-    String CRON_TYPE_PROPERTY = "cron.type";
-
-    Object getScheduler();
-
-}
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/AsyncEmbeddedCronJob.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/AsyncEmbeddedCronJob.java
index d3a7fb5..b0ca4e4 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/AsyncEmbeddedCronJob.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/AsyncEmbeddedCronJob.java
@@ -17,50 +17,64 @@
  */
 package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+
 import org.apache.felix.hc.core.impl.scheduling.AsyncJob;
-import org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Runs health checks that are configured with a cron expression for
+ * Runs health checks/monitors that are configured with a cron expression for
  * asynchronous execution.
  */
 public final class AsyncEmbeddedCronJob extends AsyncJob {
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private static final Logger LOG = LoggerFactory.getLogger(AsyncEmbeddedCronJob.class);
 
-    private final EmbeddedCronJob cronJob;
+    private final String id;
+    private final String cronExpression;
+    private final EmbeddedCronParser cronParser;
+    
     private final EmbeddedCronScheduler scheduler;
 
-    public AsyncEmbeddedCronJob(final Runnable runnable, final HealthCheckCronScheduler healtchCheckScheduler,
+    private volatile boolean executing = false;
+    private volatile long lastExecutingTime = 0;
+    private volatile long nextExecutingTime = 0;
+
+    public AsyncEmbeddedCronJob(final Runnable runnable, final EmbeddedCronSchedulerProvider embeddedCronSchedulerProvider,
             final String id, final String cronExpression) {
         super(runnable);
-        cronJob = new HealthCheckJob(cronExpression, id);
-        scheduler = (EmbeddedCronScheduler) healtchCheckScheduler.getScheduler();
+        this.id = id;
+
+        this.scheduler = embeddedCronSchedulerProvider.getScheduler();
+        
+        this.cronExpression = cronExpression;
+        this.cronParser= new EmbeddedCronParser(cronExpression);
+        nextExecutingTime = cronParser.next(System.currentTimeMillis());
     }
 
     @Override
     public boolean schedule() {
         try {
-            scheduler.schedule(cronJob);
-            logger.info("Scheduled job {} with trigger {}", cronJob.name(), cronJob.cron());
+            scheduler.schedule(this);
+            LOG.info("Scheduled job {} with trigger {}", id, cronExpression);
             return true;
         } catch (final Exception e) {
-            logger.error("Could not schedule job for {}", runnable, e);
+            LOG.error("Could not schedule job for {}", runnable, e);
             return false;
         }
     }
 
     @Override
     public boolean unschedule() {
-        final String name = cronJob.name();
-        logger.debug("Unscheduling job {}", name);
+
+        LOG.debug("Unscheduling job {}", id);
         try {
-            scheduler.remove(name);
+            scheduler.remove(this);
             return true;
         } catch (final Exception e) {
-            logger.error("Could not unschedule job for {}", name, e);
+            LOG.error("Could not unschedule job for {}", id, e);
             return false;
         }
     }
@@ -70,30 +84,51 @@ public final class AsyncEmbeddedCronJob extends AsyncJob {
         return "[Async embedded cron job for " + runnable + "]";
     }
 
-    private class HealthCheckJob implements EmbeddedCronJob {
+    public String getId() {
+        return id;
+    }
 
-        private final String name;
-        private final String cronExpression;
+    public long getLastExecutingTime() {
+        return lastExecutingTime;
+    }
 
-        public HealthCheckJob(final String cronExpression, final String name) {
-            this.name = name;
-            this.cronExpression = cronExpression;
-        }
+    public long getNextExecutingTime() {
+        return nextExecutingTime;
+    }
 
-        @Override
-        public String cron() {
-            return cronExpression;
+    public boolean isExecuting() {
+        return executing;
+    }
+    
+    public void checkAndExecute(ExecutorService tasksExecutor) {
+        final long currentTime = System.currentTimeMillis();
+        if (nextExecutingTime >= currentTime) {
+            return;
         }
-
-        @Override
-        public String name() {
-            return "job-hc-" + name;
+        if(executing) {
+            nextExecutingTime = cronParser.next(System.currentTimeMillis());
+            LOG.trace("Cron task {} would be fired but is still executing, skipping this execution, next execution: {}", id, nextExecutingTime);
+            return;
         }
 
-        @Override
-        public void run() throws Exception {
-            runnable.run();
+        try {
+            lastExecutingTime = currentTime;
+            executing = true;
+            tasksExecutor.execute(() -> {
+                try {
+                    runnable.run();
+                } catch (final Exception e) {
+                    LOG.error("Exception while executing cron task {}", id, e);
+                } finally {
+                    nextExecutingTime = cronParser.next(System.currentTimeMillis());
+                    executing = false;
+                }
+            });
+            
+        } catch (final RejectedExecutionException e) {
+            executing = false;
+            LOG.error("Failed to start cron task: {}", id, e);
         }
     }
-
+    
 }
\ No newline at end of file
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronJob.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronJob.java
deleted file mode 100644
index e6d038b..0000000
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronJob.java
+++ /dev/null
@@ -1,28 +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 SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
-
-public interface EmbeddedCronJob {
-
-    void run() throws Exception;
-
-    String cron();
-
-    String name();
-
-}
\ No newline at end of file
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParser.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParser.java
index 332629e..a545cfd 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParser.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParser.java
@@ -18,6 +18,7 @@
 package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Calendar;
 import java.util.Collections;
@@ -245,7 +246,10 @@ public final class EmbeddedCronParser {
      * Parse the given pattern expression.
      */
     private void parse(final String expression) {
-        final String[] fields = SPACE_SPLITTER.split(expression);
+        String[] fields = SPACE_SPLITTER.split(expression);
+        if (fields.length == 7 && "*".equals(fields[6])) {
+            fields = Arrays.copyOfRange(fields, 0, 6);
+        }
         if (fields.length != 6) {
             throw new IllegalArgumentException(String.format(
                     "Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
@@ -391,8 +395,6 @@ public final class EmbeddedCronParser {
             return "0 0 0 * * ?";
         case "@hourly":
             return "0 0 * * * ?";
-        case "@reboot":
-            return "0 0 0 1 1 ? 1900";
         default:
             throw new IllegalArgumentException("Unrecognized @ expression: '" + expression + "'");
         }
@@ -422,4 +424,9 @@ public final class EmbeddedCronParser {
     public String toString() {
         return getClass().getSimpleName() + ": " + expression;
     }
+
+    String getExpression() {
+        return expression;
+    }
+
 }
\ No newline at end of file
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronScheduler.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronScheduler.java
index 16c29a7..72d2b8f 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronScheduler.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronScheduler.java
@@ -17,151 +17,61 @@
  */
 package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
 
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public final class EmbeddedCronScheduler implements AutoCloseable {
+public final class EmbeddedCronScheduler implements Runnable {
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    /* Object used for internal locking/notifications */
-    private final Object monitor = new Object();
+    private static final Logger LOG = LoggerFactory.getLogger(EmbeddedCronScheduler.class);
 
     /* List of managed tasks */
-    private final Map<String, EmbeddedCronSchedulerTask> tasks = new HashMap<>();
-
-    /* Task executor instance */
-    public final ExecutorService tasksExecutor;
+    private final Map<String, AsyncEmbeddedCronJob> tasks = new ConcurrentHashMap<>();
 
-    /* Thread that makes all scheduling job */
-    public final SchedulerThread schedulerThread;
+    /* Task executor service */
+    public final ScheduledExecutorService scheduledExecutorService;
 
-    /* Flag to check if the scheduler is active */
-    private final AtomicBoolean isActive;
-
-    public EmbeddedCronScheduler(final ExecutorService tasksExecutor, final long initialDelay, final long checkInterval,
+    private String schedulerThreadName;
+    
+    public EmbeddedCronScheduler(final ScheduledExecutorService scheduledExecutorService, final long initialDelay, final long checkInterval,
             final TimeUnit timeUnit, final String schedulerThreadName) {
-        schedulerThread = new SchedulerThread(initialDelay, checkInterval, timeUnit, schedulerThreadName);
-        this.tasksExecutor = tasksExecutor;
-        isActive = new AtomicBoolean(true);
-        schedulerThread.start();
+        
+        this.scheduledExecutorService = scheduledExecutorService;
+        scheduledExecutorService.scheduleAtFixedRate(this, initialDelay, checkInterval, timeUnit);
+        
+        this.schedulerThreadName = schedulerThreadName;
     }
 
-    public void schedule(final EmbeddedCronJob cronJob) {
-        synchronized (monitor) {
-            tasks.put(cronJob.name(), new EmbeddedCronSchedulerTask(cronJob, new EmbeddedCronParser(cronJob.cron())));
-        }
+    public void schedule(AsyncEmbeddedCronJob cronJob) {
+        tasks.put(cronJob.getId(), cronJob);
     }
 
-    public void remove(final String name) {
-        synchronized (monitor) {
-            tasks.remove(name);
-        }
+    public void remove(AsyncEmbeddedCronJob cronJob) {
+        tasks.remove(cronJob.getId());
     }
 
     @Override
-    public void close() {
-        shutdown();
-    }
-
-    public void shutdown() {
-        isActive.set(false);
-        synchronized (monitor) {
-            monitor.notify();
-        }
-        schedulerThread.interrupt();
-    }
-
-    public boolean isShutdown() {
-        return !isActive.get();
-    }
-
-    public Collection<EmbeddedCronSchedulerTask> getTasks() {
-        return tasks.values();
-    }
-
-    public class SchedulerThread extends Thread {
-        public final long initialDelay;
-        public final long checkInterval;
-        public final TimeUnit timeUnit;
-
-        private SchedulerThread(final long initialDelay, final long checkInterval, final TimeUnit timeUnit,
-                final String threadName) {
-            if (initialDelay < 0) {
-                throw new IllegalArgumentException("initialDelay < 0. Value: " + initialDelay);
-            }
-            if (checkInterval <= 0) {
-                throw new IllegalArgumentException("checkInterval must be > 0. Value: " + checkInterval);
-            }
-            if (timeUnit == null) {
-                throw new IllegalArgumentException("timeUnit is null");
+    public void run() {
+        String threadNameToRestore = Thread.currentThread().getName();
+        try {
+            Thread.currentThread().setName(schedulerThreadName);
+            LOG.trace("tasks: {}", tasks);
+            
+            for (final Entry<String, AsyncEmbeddedCronJob> entry : tasks.entrySet()) {
+                final AsyncEmbeddedCronJob job = entry.getValue();
+                job.checkAndExecute(scheduledExecutorService);
             }
-            this.initialDelay = initialDelay;
-            this.checkInterval = checkInterval;
-            this.timeUnit = timeUnit;
-            setName(threadName);
-            setDaemon(true);
+        } catch (Exception e) {
+            // TODO: handle exception
+        } finally {
+            Thread.currentThread().setName(threadNameToRestore);
         }
 
-        @Override
-        public void run() {
-            if (initialDelay > 0) {
-                pause(timeUnit.toMillis(initialDelay));
-            }
-            final long checkIntervalMillis = timeUnit.toMillis(checkInterval);
-            while (isActive.get()) {
-                try {
-                    checkAndExecute();
-                    pause(checkIntervalMillis);
-                } catch (final Exception e) {
-                    logger.error("Got internal error that must never happen!", e);
-                }
-            }
-        }
-
-        private void pause(final long checkIntervalMillis) {
-            try {
-                synchronized (monitor) {
-                    monitor.wait(checkIntervalMillis);
-                }
-            } catch (final InterruptedException e) {
-                logger.error("Got unexpected interrupted exception! Ignoring", e);
-                Thread.currentThread().interrupt();
-            }
-        }
-
-        private void checkAndExecute() {
-            synchronized (monitor) {
-                final long currentTime = System.currentTimeMillis();
-                for (final Entry<String, EmbeddedCronSchedulerTask> entry : tasks.entrySet()) {
-                    final EmbeddedCronSchedulerTask task = entry.getValue();
-                    if (task.nextExecutingTime >= currentTime) {
-                        continue;
-                    }
-                    if (task.executing) {
-                        task.nextExecutingTime = task.cronParser.next(currentTime);
-                        continue;
-                    }
-                    try {
-                        task.lastExecutingTime = System.currentTimeMillis();
-                        tasksExecutor.execute(task);
-                        task.executing = true;
-                    } catch (final RejectedExecutionException e) {
-                        logger.error("Failed to start task: {}", task, e);
-                        Thread.currentThread().interrupt();
-                    }
-                }
-            }
-        }
     }
 
 }
\ No newline at end of file
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerProvider.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerProvider.java
index 22148f6..884677a 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerProvider.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerProvider.java
@@ -18,36 +18,31 @@
 package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler.CRON_TYPE_PROPERTY;
-import static org.apache.felix.hc.core.impl.scheduling.cron.embedded.EmbeddedCronSchedulerProvider.CRON_TYPE_EMBEDDED;
 
 import java.util.concurrent.TimeUnit;
 
 import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
-import org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler;
 import org.apache.felix.hc.core.impl.scheduling.cron.embedded.EmbeddedCronSchedulerProvider.Configuration;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.metatype.annotations.AttributeDefinition;
 import org.osgi.service.metatype.annotations.Designate;
 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
 @Designate(ocd = Configuration.class)
-@Component(enabled = false, property = CRON_TYPE_PROPERTY + "=" + CRON_TYPE_EMBEDDED)
-public final class EmbeddedCronSchedulerProvider implements HealthCheckCronScheduler {
+@Component(service = EmbeddedCronSchedulerProvider.class)
+public class EmbeddedCronSchedulerProvider {
 
-    public static final String CRON_TYPE_EMBEDDED = "embedded";
     public static final String HC_SCHEDULER_NAME = "felix.hc.embedded.scheduler";
 
-    @ObjectClassDefinition(name = "Embedded Cron Scheduler Configuration")
+    @ObjectClassDefinition(name = "Health Check: Embedded Cron Scheduler")
     @interface Configuration {
-        @AttributeDefinition(name = "Initial Delay", description = "The initial delay before the embedded cron scheduler thread becomes active")
-        long initialDelay() default 10L;
+        @AttributeDefinition(name = "Initial Delay", description = "The initial delay before tasks are scheduled")
+        long initialDelay() default 3L;
 
         @AttributeDefinition(name = "Check Interval Delay", description = "The delay to check for a scheduled task to satisfy the cron expression")
-        long checkInterval() default 3L;
+        long checkInterval() default 1L;
 
         @AttributeDefinition(name = "Time unit", description = "The time unit of the delays as specified in this configuration")
         TimeUnit timeUnit() default SECONDS;
@@ -56,30 +51,37 @@ public final class EmbeddedCronSchedulerProvider implements HealthCheckCronSched
         String schedulerName() default HC_SCHEDULER_NAME;
     }
 
-    private EmbeddedCronScheduler scheduler;
 
     @Reference
     private HealthCheckExecutorThreadPool threadPool;
 
+    private Configuration configuration;
+    
+    private EmbeddedCronScheduler scheduler = null;
+    
     @Activate
     void activate(final Configuration configuration) {
-        scheduler = new EmbeddedCronScheduler( //
-                threadPool.getExecutor(), //
-                configuration.initialDelay(), //
-                configuration.checkInterval(), //
-                configuration.timeUnit(), //
-                configuration.schedulerName());
+        this.configuration = configuration;
     }
 
-    @Deactivate
-    void deactivate() {
-        scheduler.shutdown();
-        scheduler = null;
-    }
-
-    @Override
-    public EmbeddedCronScheduler getScheduler() {
+    public synchronized EmbeddedCronScheduler getScheduler() {
+        if(scheduler == null) {
+            scheduler = new EmbeddedCronScheduler(
+                    threadPool.getExecutor(),
+                    configuration.initialDelay(),
+                    configuration.checkInterval(),
+                    configuration.timeUnit(),
+                    configuration.schedulerName());
+        }
         return scheduler;
+
     }
 
+    
+    // @Deactivate
+    // no shutdown of scheduler needed since HealthCheckExecutorThreadPool is handling the shutdown 
+    // of the full ScheduledThreadPoolExecutor
+
+
+
 }
\ No newline at end of file
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerTask.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerTask.java
deleted file mode 100644
index 7632254..0000000
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerTask.java
+++ /dev/null
@@ -1,67 +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 SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-final class EmbeddedCronSchedulerTask implements Runnable {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    final EmbeddedCronJob cronJob;
-    final EmbeddedCronParser cronParser;
-
-    volatile boolean executing;
-    volatile long lastExecutingTime = 0;
-    volatile long nextExecutingTime = 0;
-
-    public EmbeddedCronSchedulerTask(final EmbeddedCronJob cronJob, final EmbeddedCronParser cronParser) {
-        this.cronJob = cronJob;
-        this.cronParser = cronParser;
-    }
-
-    @Override
-    public void run() {
-        try {
-            cronJob.run();
-        } catch (final Exception e) {
-            logger.error("Exception while executing cron task", e);
-        } finally {
-            nextExecutingTime = cronParser.next(System.currentTimeMillis());
-            executing = false;
-        }
-    }
-
-    public long getLastExecutingTime() {
-        return lastExecutingTime;
-    }
-
-    public long getNextExecutingTime() {
-        return nextExecutingTime;
-    }
-
-    public boolean isExecuting() {
-        return executing;
-    }
-
-    @Override
-    public String toString() {
-        return "EmbeddedCronSchedulerTask [" + cronJob.name() + "]";
-    }
-}
\ No newline at end of file
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/AsyncQuartzCronJob.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/AsyncQuartzCronJob.java
index 69c3d22..67046cb 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/AsyncQuartzCronJob.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/AsyncQuartzCronJob.java
@@ -22,7 +22,6 @@ import static org.quartz.JobBuilder.newJob;
 import static org.quartz.TriggerBuilder.newTrigger;
 
 import org.apache.felix.hc.core.impl.scheduling.AsyncJob;
-import org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler;
 import org.quartz.CronTrigger;
 import org.quartz.Job;
 import org.quartz.JobDataMap;
@@ -35,7 +34,7 @@ import org.quartz.SchedulerException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** Runs health checks that are configured with a cron expression for asynchronous execution. 
+/** Async job to be used by async health checks and async monitor. 
  * 
  * This implementation uses quartz to support the cron syntax (which is not supported by executors from standard java java.util.concurrent
  * package) */
@@ -44,16 +43,17 @@ public class AsyncQuartzCronJob extends AsyncJob {
 
     private static final String JOB_DATA_KEY_JOB = "asyncHcJob";
 
-    protected final QuartzCronScheduler quartzCronScheduler;
     private final String id;
     private final String group;
     private final String cronExpression;
 
-    private JobKey jobKey;
+    private final Scheduler quartzScheduler;
 
-    public AsyncQuartzCronJob(Runnable runnable, HealthCheckCronScheduler quartzCronSchedulerProvider, String id, String group, String cronExpression) {
+    private JobKey jobKey = null;
+
+    public AsyncQuartzCronJob(Runnable runnable, QuartzCronSchedulerProvider quartzCronSchedulerProvider, String id, String group, String cronExpression) {
         super(runnable);
-        this.quartzCronScheduler = (QuartzCronScheduler) quartzCronSchedulerProvider.getScheduler();
+        this.quartzScheduler = quartzCronSchedulerProvider.getQuartzCronScheduler().getScheduler();
         this.id = id;
         this.group = group;
         this.cronExpression = cronExpression;
@@ -77,28 +77,28 @@ public class AsyncQuartzCronJob extends AsyncJob {
     }
 
     public boolean schedule() {
+
         try {
-            Scheduler scheduler = quartzCronScheduler.getScheduler();
 
             JobDetail job = getQuartzJobDetail();
             CronTrigger cronTrigger = newTrigger().withSchedule(cronSchedule(cronExpression)).forJob(job)
                     .build();
 
-            scheduler.scheduleJob(job, cronTrigger);
+            quartzScheduler.scheduleJob(job, cronTrigger);
             LOG.info("Scheduled job {} with trigger {}", job, cronTrigger);
             return true;
         } catch (SchedulerException e) {
             LOG.error("Could not schedule job for " + runnable + ": " + e, e);
             return false;
         }
+
     }
 
     @Override
     public boolean unschedule() {
-        Scheduler scheduler = quartzCronScheduler.getScheduler();
         LOG.debug("Unscheduling job {}", jobKey);
         try {
-            scheduler.deleteJob(jobKey);
+            quartzScheduler.deleteJob(jobKey);
             return true;
         } catch (SchedulerException e) {
             LOG.error("Could not unschedule job for " + jobKey + ": " + e, e);
@@ -108,7 +108,7 @@ public class AsyncQuartzCronJob extends AsyncJob {
 
     @Override
     public String toString() {
-        return "[Async quartz cron job for " + runnable + "]";
+        return "[Async quartz job for " + runnable + "]";
     }
     
     // quartz forces to pass in a class object (and not an instance), hence this helper class is needed
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/CronJobFactory.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/CronJobFactory.java
deleted file mode 100644
index d47e343..0000000
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/CronJobFactory.java
+++ /dev/null
@@ -1,47 +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 SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.felix.hc.core.impl.scheduling.cron.quartz;
-
-import static org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler.CRON_TYPE_PROPERTY;
-import static org.apache.felix.hc.core.impl.scheduling.cron.quartz.QuartzCronSchedulerProvider.CRON_TYPE_QUARTZ;
-
-import org.apache.felix.hc.core.impl.scheduling.AsyncJob;
-import org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler;
-import org.apache.felix.hc.core.impl.scheduling.cron.embedded.AsyncEmbeddedCronJob;
-import org.osgi.service.component.ComponentServiceObjects;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-@Component(service = CronJobFactory.class)
-public final class CronJobFactory {
-
-    @Reference
-    private ComponentServiceObjects<HealthCheckCronScheduler> cronSchedulerProvider;
-
-    public AsyncJob createNewJob(final Runnable runnable, final String id, final String group,
-            final String cronExpression) {
-        final String property = (String) cronSchedulerProvider.getServiceReference().getProperty(CRON_TYPE_PROPERTY);
-        final HealthCheckCronScheduler cronScheduler = cronSchedulerProvider.getService();
-        if (CRON_TYPE_QUARTZ.equals(property)) {
-            return new AsyncQuartzCronJob(runnable, cronScheduler, id, group, cronExpression);
-        } else {
-            return new AsyncEmbeddedCronJob(runnable, cronScheduler, id, cronExpression);
-        }
-    }
-
-}
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronScheduler.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronScheduler.java
index 7325910..8033984 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronScheduler.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronScheduler.java
@@ -26,6 +26,7 @@ import org.quartz.spi.ThreadPool;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/** Wrapper around org.quartz.Scheduler  */
 public class QuartzCronScheduler {
     private static final Logger LOG = LoggerFactory.getLogger(QuartzCronScheduler.class);
 
diff --git a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronSchedulerProvider.java b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronSchedulerProvider.java
index e80c034..ef9c545 100644
--- a/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronSchedulerProvider.java
+++ b/healthcheck/core/src/main/java/org/apache/felix/hc/core/impl/scheduling/cron/quartz/QuartzCronSchedulerProvider.java
@@ -17,42 +17,29 @@
  */
 package org.apache.felix.hc.core.impl.scheduling.cron.quartz;
 
-import static org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler.CRON_TYPE_PROPERTY;
-import static org.apache.felix.hc.core.impl.scheduling.cron.quartz.QuartzCronSchedulerProvider.CRON_TYPE_QUARTZ;
-
 import org.apache.felix.hc.core.impl.executor.HealthCheckExecutorThreadPool;
-import org.apache.felix.hc.core.impl.scheduling.cron.HealthCheckCronScheduler;
-import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/**
- * Component without direct quartz imports (can always start) that will provide
- * a QuartzCronScheduler on demand.
- */
-@Component(enabled = false, property = CRON_TYPE_PROPERTY + "=" + CRON_TYPE_QUARTZ)
-public class QuartzCronSchedulerProvider implements HealthCheckCronScheduler {
+/** Component without direct quartz imports (can always start) that will provide a QuartzCronScheduler on demand. */
+@Component(service = QuartzCronSchedulerProvider.class)
+public class QuartzCronSchedulerProvider {
 
-    public static final String CRON_TYPE_QUARTZ = "quartz";
-    
     private static final Logger LOG = LoggerFactory.getLogger(QuartzCronSchedulerProvider.class);
 
-    private QuartzCronScheduler quartzCronScheduler;
+    private QuartzCronScheduler quartzCronScheduler = null;
 
     @Reference
     HealthCheckExecutorThreadPool healthCheckExecutorThreadPool;
-    
-    @Activate
-    void activate() {
-        quartzCronScheduler = new QuartzCronScheduler(healthCheckExecutorThreadPool);
-        LOG.info("Created quartz scheduler health check core bundle");
-    }
 
-    @Override
-    public QuartzCronScheduler getScheduler() {
+    public synchronized QuartzCronScheduler getQuartzCronScheduler()  {
+        if (quartzCronScheduler == null) {
+            quartzCronScheduler = new QuartzCronScheduler(healthCheckExecutorThreadPool);
+            LOG.info("Created QuartzCronScheduler");
+        }
         return quartzCronScheduler;
     }
 
@@ -61,7 +48,7 @@ public class QuartzCronSchedulerProvider implements HealthCheckCronScheduler {
         if (quartzCronScheduler != null) {
             quartzCronScheduler.shutdown();
             quartzCronScheduler = null;
-            LOG.info("QuartzCronScheduler shutdown");
+            LOG.info("Shutdown of QuartzCronScheduler");
         }
     }
 
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParserTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParserTest.java
index 4cd28ed..48af03c 100644
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParserTest.java
+++ b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronParserTest.java
@@ -19,39 +19,792 @@ package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
 
 import static org.junit.Assert.assertEquals;
 
+import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.TimeZone;
 
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-@SuppressWarnings("deprecation")
+@RunWith(Parameterized.class)
 public class EmbeddedCronParserTest {
 
-    @Test
-    public void testAt50Seconds() {
-        assertEquals(new Date(2012, 6, 2, 1, 0).getTime(),
-                new EmbeddedCronParser("*/15 * 1-4 * * *").next(new Date(2012, 6, 1, 9, 53, 50).getTime()));
+    private final Calendar calendar = new GregorianCalendar();
+
+    private final Date date;
+
+    private final TimeZone timeZone;
+
+    public EmbeddedCronParserTest(final Date date, final TimeZone timeZone) {
+        this.date = date;
+        this.timeZone = timeZone;
+    }
+
+    @Parameters(name = "date [{0}], time zone [{1}]")
+    public static List<Object[]> getParameters() {
+        final List<Object[]> list = new ArrayList<>();
+        list.add(new Object[] { new Date(), TimeZone.getTimeZone("PST") });
+        list.add(new Object[] { new Date(), TimeZone.getTimeZone("CET") });
+        return list;
+    }
+
+    private static void roundup(final Calendar calendar) {
+        calendar.add(Calendar.SECOND, 1);
+        calendar.set(Calendar.MILLISECOND, 0);
+    }
+
+    @Before
+    public void setUp() {
+        calendar.setTimeZone(timeZone);
+        calendar.setTime(date);
+        roundup(calendar);
     }
 
     @Test
-    public void testAt0Seconds() {
-        assertEquals(new Date(2012, 6, 2, 1, 0).getTime(),
-                new EmbeddedCronParser("*/15 * 1-4 * * *").next(new Date(2012, 6, 1, 9, 53).getTime()));
+    public void testAt0And15And45Seconds() {
+        
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("*/15 * 1-4 * * *", timeZone);
+
+        calendar.set(Calendar.DAY_OF_MONTH, 2);
+        calendar.set(Calendar.HOUR_OF_DAY, 9);
+        calendar.set(Calendar.MINUTE, 53);
+        calendar.set(Calendar.SECOND, 50);
+
+        Date intialTime = calendar.getTime();
+        calendar.set(Calendar.DAY_OF_MONTH, 3);
+        calendar.set(Calendar.HOUR_OF_DAY, 1);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTime().getTime(), trigger.next(intialTime.getTime()));
+
+        intialTime = calendar.getTime();
+        calendar.set(Calendar.SECOND, 15);
+        assertEquals(calendar.getTime().getTime(), trigger.next(intialTime.getTime()));
+
+        intialTime = calendar.getTime();
+        calendar.set(Calendar.SECOND, 30);
+        assertEquals(calendar.getTime().getTime(), trigger.next(intialTime.getTime()));
+
+        intialTime = calendar.getTime();
+        calendar.set(Calendar.SECOND, 45);
+        assertEquals(calendar.getTime().getTime(), trigger.next(intialTime.getTime()));
+
     }
 
+
     @Test
     public void testAt0Minutes() {
-        assertEquals(new Date(2012, 6, 2, 1, 0).getTime(),
-                new EmbeddedCronParser("0 */2 1-4 * * *").next(new Date(2012, 6, 1, 9, 0).getTime()));
+
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 */2 1-4 * * *", timeZone);
+
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 9);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+
+        Date intialTime = calendar.getTime();
+        calendar.set(Calendar.DAY_OF_MONTH, 2);
+        calendar.set(Calendar.HOUR_OF_DAY, 1);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        long next = trigger.next(intialTime.getTime());
+        assertEquals(calendar.getTime().getTime(), next);
+
+
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testWith0Increment() {
-        new EmbeddedCronParser("*/0 * * * * *").next(new Date(2012, 6, 1, 9, 0).getTime());
+
+        new EmbeddedCronParser("*/0 * * * * *").next(calendar.getTime().getTime());
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testWithNegativeIncrement() {
-        new EmbeddedCronParser("*/-1 * * * * *").next(new Date(2012, 6, 1, 9, 0).getTime());
+        new EmbeddedCronParser("*/-1 * * * * *").next(calendar.getTime().getTime());
+    }
+    
+    @Test
+    public void testMatchAll() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * *", timeZone);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testMatchLastSecond() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * *", timeZone);
+        calendar.set(Calendar.SECOND, 58);
+        assertMatchesNextSecond(trigger, calendar);
+    }
+
+    @Test
+    public void testMatchSpecificSecond() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("10 * * * * *", timeZone);
+        calendar.set(Calendar.SECOND, 9);
+        assertMatchesNextSecond(trigger, calendar);
+    }
+
+    @Test
+    public void testIncrementSecondByOne() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("11 * * * * *", timeZone);
+        calendar.set(Calendar.SECOND, 10);
+        final Date date = calendar.getTime();
+        calendar.add(Calendar.SECOND, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementSecondWithPreviousExecutionTooEarly() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("11 * * * * *", timeZone);
+        calendar.set(Calendar.SECOND, 11);
+        final Date date = calendar.getTime();
+
+        calendar.add(Calendar.MINUTE, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementSecondAndRollover() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("10 * * * * *", timeZone);
+        calendar.set(Calendar.SECOND, 11);
+        final Date date = calendar.getTime();
+        calendar.add(Calendar.SECOND, 59);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testSecondRange() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("10-15 * * * * *", timeZone);
+        calendar.set(Calendar.SECOND, 9);
+        assertMatchesNextSecond(trigger, calendar);
+        calendar.set(Calendar.SECOND, 14);
+        assertMatchesNextSecond(trigger, calendar);
+    }
+
+    @Test
+    public void testIncrementMinute() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 * * * * *", timeZone);
+        calendar.set(Calendar.MINUTE, 10);
+        Date date = calendar.getTime();
+        calendar.add(Calendar.MINUTE, 1);
+        calendar.set(Calendar.SECOND, 0);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        calendar.add(Calendar.MINUTE, 1);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+    }
+
+    @Test
+    public void testIncrementMinuteByOne() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 11 * * * *", timeZone);
+        calendar.set(Calendar.MINUTE, 10);
+        final Date date = calendar.getTime();
+
+        calendar.add(Calendar.MINUTE, 1);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementMinuteAndRollover() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 10 * * * *", timeZone);
+        calendar.set(Calendar.MINUTE, 11);
+        calendar.set(Calendar.SECOND, 0);
+        final Date date = calendar.getTime();
+        calendar.add(Calendar.MINUTE, 59);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementHour() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 * * * *", timeZone);
+        calendar.set(Calendar.MONTH, 9);
+        calendar.set(Calendar.DAY_OF_MONTH, 30);
+        calendar.set(Calendar.HOUR_OF_DAY, 11);
+        calendar.set(Calendar.MINUTE, 1);
+        calendar.set(Calendar.SECOND, 0);
+        Date date = calendar.getTime();
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.HOUR_OF_DAY, 12);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        calendar.set(Calendar.HOUR_OF_DAY, 13);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementHourAndRollover() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 * * * *", timeZone);
+        calendar.set(Calendar.MONTH, 9);
+        calendar.set(Calendar.DAY_OF_MONTH, 10);
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
+        calendar.set(Calendar.MINUTE, 1);
+        calendar.set(Calendar.SECOND, 0);
+        Date date = calendar.getTime();
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.DAY_OF_MONTH, 11);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        calendar.set(Calendar.HOUR_OF_DAY, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementDayOfMonth() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        Date date = calendar.getTime();
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        assertEquals(2, calendar.get(Calendar.DAY_OF_MONTH));
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        assertEquals(3, calendar.get(Calendar.DAY_OF_MONTH));
+    }
+
+    @Test
+    public void testIncrementDayOfMonthByOne() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * 10 * *", timeZone);
+        calendar.set(Calendar.DAY_OF_MONTH, 9);
+        final Date date = calendar.getTime();
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementDayOfMonthAndRollover() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * 10 * *", timeZone);
+        calendar.set(Calendar.DAY_OF_MONTH, 11);
+        final Date date = calendar.getTime();
+        calendar.add(Calendar.MONTH, 1);
+        calendar.set(Calendar.DAY_OF_MONTH, 10);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testDailyTriggerInShortMonth() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
+        calendar.set(Calendar.MONTH, 8); // September: 30 days
+        calendar.set(Calendar.DAY_OF_MONTH, 30);
+        Date date = calendar.getTime();
+        calendar.set(Calendar.MONTH, 9); // October
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.set(Calendar.DAY_OF_MONTH, 2);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testDailyTriggerInLongMonth() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
+        calendar.set(Calendar.MONTH, 7); // August: 31 days and not a daylight saving boundary
+        calendar.set(Calendar.DAY_OF_MONTH, 30);
+        Date date = calendar.getTime();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.DAY_OF_MONTH, 31);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.set(Calendar.MONTH, 8); // September
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testDailyTriggerOnDaylightSavingBoundary() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
+        calendar.set(Calendar.MONTH, 9); // October: 31 days and a daylight saving boundary in CET
+        calendar.set(Calendar.DAY_OF_MONTH, 30);
+        Date date = calendar.getTime();
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.DAY_OF_MONTH, 31);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.set(Calendar.MONTH, 10); // November
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementMonth() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 1 * *", timeZone);
+        calendar.set(Calendar.MONTH, 9);
+        calendar.set(Calendar.DAY_OF_MONTH, 30);
+        Date date = calendar.getTime();
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MONTH, 10);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.set(Calendar.MONTH, 11);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementMonthAndRollover() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 1 * *", timeZone);
+        calendar.set(Calendar.MONTH, 11);
+        calendar.set(Calendar.DAY_OF_MONTH, 31);
+        calendar.set(Calendar.YEAR, 2010);
+        Date date = calendar.getTime();
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MONTH, 0);
+        calendar.set(Calendar.YEAR, 2011);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.set(Calendar.MONTH, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testMonthlyTriggerInLongMonth() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 31 * *", timeZone);
+        calendar.set(Calendar.MONTH, 9);
+        calendar.set(Calendar.DAY_OF_MONTH, 30);
+        final Date date = calendar.getTime();
+        calendar.set(Calendar.DAY_OF_MONTH, 31);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testMonthlyTriggerInShortMonth() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 1 * *", timeZone);
+        calendar.set(Calendar.MONTH, 9);
+        calendar.set(Calendar.DAY_OF_MONTH, 30);
+        final Date date = calendar.getTime();
+        calendar.set(Calendar.MONTH, 10);
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testIncrementDayOfWeekByOne() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * 2", timeZone);
+        calendar.set(Calendar.DAY_OF_WEEK, 2);
+        final Date date = calendar.getTime();
+        calendar.add(Calendar.DAY_OF_WEEK, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+        assertEquals(Calendar.TUESDAY, calendar.get(Calendar.DAY_OF_WEEK));
+    }
+
+    @Test
+    public void testIncrementDayOfWeekAndRollover() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * 2", timeZone);
+        calendar.set(Calendar.DAY_OF_WEEK, 4);
+        final Date date = calendar.getTime();
+        calendar.add(Calendar.DAY_OF_MONTH, 6);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+        assertEquals(Calendar.TUESDAY, calendar.get(Calendar.DAY_OF_WEEK));
+    }
+
+    @Test
+    public void testSpecificMinuteSecond() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("55 5 * * * *", timeZone);
+        calendar.set(Calendar.MINUTE, 4);
+        calendar.set(Calendar.SECOND, 54);
+        Date date = calendar.getTime();
+
+        calendar.add(Calendar.MINUTE, 1);
+        calendar.set(Calendar.SECOND, 55);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.add(Calendar.HOUR, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testSpecificHourSecond() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("55 * 10 * * *", timeZone);
+        calendar.set(Calendar.HOUR_OF_DAY, 9);
+        calendar.set(Calendar.SECOND, 54);
+        Date date = calendar.getTime();
+
+        calendar.add(Calendar.HOUR_OF_DAY, 1);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 55);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.add(Calendar.MINUTE, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testSpecificMinuteHour() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* 5 10 * * *", timeZone);
+        calendar.set(Calendar.MINUTE, 4);
+        calendar.set(Calendar.HOUR_OF_DAY, 9);
+        Date date = calendar.getTime();
+        calendar.add(Calendar.MINUTE, 1);
+        calendar.add(Calendar.HOUR_OF_DAY, 1);
+        calendar.set(Calendar.SECOND, 0);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        // next trigger is in one second because second is wildcard
+        calendar.add(Calendar.SECOND, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testSpecificDayOfMonthSecond() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("55 * * 3 * *", timeZone);
+        calendar.set(Calendar.DAY_OF_MONTH, 2);
+        calendar.set(Calendar.SECOND, 54);
+        Date date = calendar.getTime();
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 55);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.add(Calendar.MINUTE, 1);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void testSpecificDate() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * 3 11 *", timeZone);
+        calendar.set(Calendar.DAY_OF_MONTH, 2);
+        calendar.set(Calendar.MONTH, 9);
+        Date date = calendar.getTime();
+
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MONTH, 10); // 10=November
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+
+        calendar.add(Calendar.SECOND, 1);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNonExistentSpecificDate() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 31 6 *", timeZone);
+        calendar.set(Calendar.DAY_OF_MONTH, 10);
+        calendar.set(Calendar.MONTH, 2);
+        final Date date = calendar.getTime();
+        trigger.next(date.getTime());
+    }
+
+    @Test
+    public void testLeapYearSpecificDate() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 29 2 *", timeZone);
+        calendar.set(Calendar.YEAR, 2007);
+        calendar.set(Calendar.DAY_OF_MONTH, 10);
+        calendar.set(Calendar.MONTH, 1); // 2=February
+        Date date = calendar.getTime();
+
+        calendar.set(Calendar.YEAR, 2008);
+        calendar.set(Calendar.DAY_OF_MONTH, 29);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        calendar.add(Calendar.YEAR, 4);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+    }
+
+    @Test
+    public void testWeekDaySequence() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 7 ? * MON-FRI", timeZone);
+        // This is a Saturday
+        calendar.set(2009, Calendar.SEPTEMBER, 26);
+        Date date = calendar.getTime();
+        // 7 am is the trigger time
+        calendar.set(Calendar.HOUR_OF_DAY, 7);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        // Add two days because we start on Saturday
+        calendar.add(Calendar.DAY_OF_MONTH, 2);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        // Next day is a week day so add one
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTimeInMillis(), date.getTime());
+    }
+
+    @Test
+    public void testDayOfWeekIndifferent() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * 2 * *", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * 2 * ?", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testSecondIncrementer() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("57,59 * * * * *", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("57/2 * * * * *", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testSecondIncrementerWithRange() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("1,3,5 * * * * *", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("1-6/2 * * * * *", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testHourIncrementer() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * 4,8,12,16,20 * * *", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * 4/4 * * *", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testDayNames() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * * 0-6", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * * TUE,WED,THU,FRI,SAT,SUN,MON", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testSundayIsZero() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * * 0", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * * SUN", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testSundaySynonym() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * * 0", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * * 7", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testMonthNames() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * 1-12 *", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser(
+                "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testMonthNamesMixedCase() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * 2 *", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * Feb *", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSecondInvalid() {
+        new EmbeddedCronParser("77 * * * * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSecondRangeInvalid() {
+        new EmbeddedCronParser("44-77 * * * * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMinuteInvalid() {
+        new EmbeddedCronParser("* 77 * * * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMinuteRangeInvalid() {
+        new EmbeddedCronParser("* 44-77 * * * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHourInvalid() {
+        new EmbeddedCronParser("* * 27 * * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testHourRangeInvalid() {
+        new EmbeddedCronParser("* * 23-28 * * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDayInvalid() {
+        new EmbeddedCronParser("* * * 45 * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDayRangeInvalid() {
+        new EmbeddedCronParser("* * * 28-45 * *", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMonthInvalid() {
+        new EmbeddedCronParser("0 0 0 25 13 ?", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMonthInvalidTooSmall() {
+        new EmbeddedCronParser("0 0 0 25 0 ?", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDayOfMonthInvalid() {
+        new EmbeddedCronParser("0 0 0 32 12 ?", timeZone);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMonthRangeInvalid() {
+        new EmbeddedCronParser("* * * * 11-13 *", timeZone);
+    }
+
+    @Test
+    public void testWhitespace() {
+        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("*  *  * *  1 *", timeZone);
+        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * 1 *", timeZone);
+        assertEquals(trigger1, trigger2);
+    }
+
+    @Test
+    public void testMonthSequence() {
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 30 23 30 1/3 ?", timeZone);
+        calendar.set(2010, Calendar.DECEMBER, 30);
+        Date date = calendar.getTime();
+        // set expected next trigger time
+        calendar.set(Calendar.HOUR_OF_DAY, 23);
+        calendar.set(Calendar.MINUTE, 30);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.add(Calendar.MONTH, 1);
+
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTime(), date);
+
+        // Next trigger is 3 months latter
+        calendar.add(Calendar.MONTH, 3);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTime(), date);
+
+        // Next trigger is 3 months latter
+        calendar.add(Calendar.MONTH, 3);
+        date = new Date(trigger.next(date.getTime()));
+        assertEquals(calendar.getTime(), date);
+    }
+
+    @Test
+    public void testDaylightSavingMissingHour() {
+        // This trigger has to be somewhere in between 2am and 3am
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 10 2 * * *", timeZone);
+        calendar.set(Calendar.DAY_OF_MONTH, 31);
+        calendar.set(Calendar.MONTH, Calendar.MARCH);
+        calendar.set(Calendar.YEAR, 2013);
+        calendar.set(Calendar.HOUR_OF_DAY, 1);
+        calendar.set(Calendar.SECOND, 54);
+        final Date date = calendar.getTime();
+        if (timeZone.equals(TimeZone.getTimeZone("CET"))) {
+            // Clocks go forward an hour so 2am doesn't exist in CET for this date
+            calendar.add(Calendar.DAY_OF_MONTH, 1);
+        }
+        calendar.add(Calendar.HOUR_OF_DAY, 1);
+        calendar.set(Calendar.MINUTE, 10);
+        calendar.set(Calendar.SECOND, 0);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
+    }
+
+    @Test
+    public void test7FieldsWithYearSetToStar() {
+
+        final EmbeddedCronParser trigger = new EmbeddedCronParser("30 30 7 * * * *", timeZone);
+
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 2);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+
+        Date intialTime = calendar.getTime();
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 7);
+        calendar.set(Calendar.MINUTE, 30);
+        calendar.set(Calendar.SECOND, 30);
+        long next = trigger.next(intialTime.getTime());
+        assertEquals(calendar.getTime().getTime(), next);
+        
+    }
+    
+    @Test
+    public void testPreDeclared() {
+        assertEquals("0 0 0 1 1 *", new EmbeddedCronParser("@annually", timeZone).getExpression());
+        assertEquals("0 0 0 1 1 *", new EmbeddedCronParser("@yearly", timeZone).getExpression());
+        assertEquals("0 0 0 1 * *", new EmbeddedCronParser("@monthly", timeZone).getExpression());
+        assertEquals("0 0 0 ? * MON", new EmbeddedCronParser("@weekly", timeZone).getExpression());
+        assertEquals("0 0 0 * * ?", new EmbeddedCronParser("@daily", timeZone).getExpression());
+        assertEquals("0 0 * * * ?", new EmbeddedCronParser("@hourly", timeZone).getExpression());
+    }
+
+    private void assertMatchesNextSecond(final EmbeddedCronParser trigger, final Calendar calendar) {
+        final Date date = calendar.getTime();
+        roundup(calendar);
+        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
     }
 
 }
\ No newline at end of file
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerTest.java
index f8ba8f1..e222da8 100644
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerTest.java
+++ b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronSchedulerTest.java
@@ -18,114 +18,130 @@
 package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.MockitoAnnotations.initMocks;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.felix.hc.core.impl.scheduling.cron.embedded.EmbeddedSampleCronJob1.Callback;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Spy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class EmbeddedCronSchedulerTest {
+    private static final Logger LOG = LoggerFactory.getLogger(EmbeddedCronScheduler.class);
+
+    @Spy
+    EmbeddedCronSchedulerProvider embeddedCronSchedulerProvider = new EmbeddedCronSchedulerProvider();
+    
+    EmbeddedCronScheduler cronScheduler;
+    
+    ScheduledExecutorService scheduledExecutorService;
+    
+    @Before
+    public void setup() {
+        initMocks(this);
+        
+        scheduledExecutorService = Executors.newScheduledThreadPool(10);
+        cronScheduler = new EmbeddedCronScheduler(scheduledExecutorService, 0, 50, TimeUnit.MILLISECONDS /* for exact fires in JUnit */, "T1");
+        doReturn(cronScheduler).when(embeddedCronSchedulerProvider).getScheduler();
+    }
 
-    private static final Object SYNC_OBJECT = new Object();
-
-    @Test
-    public void checkSchedulerCheckSingleThread() throws InterruptedException {
-        try (final EmbeddedCronScheduler scheduler = new EmbeddedCronScheduler(Executors.newFixedThreadPool(1), 0, 1,
-                TimeUnit.SECONDS, "T1")) {
-            final int nIterations = 3;
-
-            final EmbeddedSampleCronJob1 service = new EmbeddedSampleCronJob2(s -> {
-                if (s.counter == nIterations) {
-                    scheduler.shutdown();
-                    synchronized (SYNC_OBJECT) {
-                        SYNC_OBJECT.notify();
-                    }
-                }
-            });
-            assertEquals(0, service.counter);
-            final long t0 = System.currentTimeMillis();
-            scheduler.schedule(service);
-
-            synchronized (SYNC_OBJECT) {
-                SYNC_OBJECT.wait(5_000L);
-            }
-            final int counter = service.counter;
-            final long totalTime = System.currentTimeMillis() - t0;
-
-            assertEquals(nIterations, counter);
-            assertTrue(scheduler.isShutdown());
-            assertTrue(totalTime < 4_000);
+    @After
+    public void teardown() {
+        scheduledExecutorService.shutdown();
+    }
+    
+    public static class SampleCounterJob implements Runnable {
+
+        public volatile int counter = 0;
+        
+        private final String name;
+        
+        public SampleCounterJob(String name) {
+            this.name = name;
+        }
 
-            Thread.sleep(2_000L);
-            assertEquals(counter, service.counter);
+        @Override
+        public void run() {
+            counter++;
+            LOG.info("{}  Name: {} counter: {}", new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()), name, counter);
         }
+
     }
 
+
     @Test
     public void checkSchedulerCheckMultipleThreads() throws InterruptedException {
-        try (EmbeddedCronScheduler scheduler = new EmbeddedCronScheduler(Executors.newFixedThreadPool(10), 0, 1,
-                TimeUnit.SECONDS, "T2")) {
-            EmbeddedSampleCronJob1.staticCounter = 0;
-            final int nIterations = 3;
-
-            final Callback c = s -> {
-                try {
-                    Thread.sleep(3000);
-                } catch (final InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                if (EmbeddedSampleCronJob1.staticCounter == nIterations) {
-                    synchronized (SYNC_OBJECT) {
-                        SYNC_OBJECT.notify();
-                    }
-                }
-            };
-            synchronized (SYNC_OBJECT) {
-                for (int i = 0; i < 10; i++) {
-                    scheduler.schedule(new EmbeddedSampleCronJob1(c, "abc" + i));
-                }
-                SYNC_OBJECT.wait(5 * 1000L);
-            }
-            scheduler.shutdown();
-            assertTrue("Number of iterations: " + EmbeddedSampleCronJob1.staticCounter + ">=" + nIterations,
-                    EmbeddedSampleCronJob1.staticCounter >= nIterations);
+
+        List<SampleCounterJob> counterRunnables = new ArrayList<>();
+        for (int i = 1; i <= 5; i++) {
+
+            String name = "abc" + i;
+            SampleCounterJob counterRunnable = new SampleCounterJob(name);
+            AsyncEmbeddedCronJob job = new AsyncEmbeddedCronJob(counterRunnable, embeddedCronSchedulerProvider, name, "* * * * * *");
+            job.schedule();
+            counterRunnables.add(counterRunnable);
+        }
+        
+        int waitForSeconds = 3;
+        try {
+            Thread.sleep(waitForSeconds * 1000); 
+        } catch (final InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        
+        for (SampleCounterJob counterRunnable : counterRunnables) {
+            // expect each to fire waitForSeconds times for cron "* * * * * *"
+            // no matter at what exact milliseconds we are
+            assertEquals(waitForSeconds, counterRunnable.counter);
         }
+        
+        
+
     }
 
+
     @Test
     public void checkScheduledPeriodsOverlap() throws InterruptedException {
-        final int callCostInSeconds = 3;
+        final int callCostInMs = 1500;
         final AtomicInteger count = new AtomicInteger();
-        try (EmbeddedCronScheduler scheduler = new EmbeddedCronScheduler(Executors.newFixedThreadPool(1), 0, 200,
-                TimeUnit.MILLISECONDS, "T10")) {
-            final EmbeddedCronJob scheduled = new EmbeddedCronJob() {
-                @Override
-                public void run() throws Exception {
-                    count.incrementAndGet();
-                    Thread.sleep(callCostInSeconds * 1000);
-                }
-
-                @Override
-                public String cron() {
-                    return "* * * * * *";
-                }
-
-                @Override
-                public String name() {
-                    return "test";
-                }
-            };
-            scheduler.schedule(scheduled);
-
-            final int nCyclesToCheck = 5;
-            Thread.sleep(nCyclesToCheck * callCostInSeconds * 1000L);
-            final int minCyclesExpected = nCyclesToCheck - 1;
-            assertTrue(
-                    "At least " + minCyclesExpected + " cycles must be started at this point, actual: " + count.get(),
-                    count.get() == nCyclesToCheck || count.get() == nCyclesToCheck - 1);
-        }
+
+        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
+        LOG.info("Test Start {}", simpleDateFormat.format(new Date()));
+        
+        Runnable jobRunnable = () -> {
+            count.incrementAndGet();
+            LOG.info("Execution Start {}", simpleDateFormat.format(new Date()));
+            try {
+                Thread.sleep(callCostInMs);
+            } catch (InterruptedException e) {
+                throw new IllegalStateException(e);
+            }
+            LOG.info("Execution End {}", simpleDateFormat.format(new Date()));
+        };
+        
+        AsyncEmbeddedCronJob job = new AsyncEmbeddedCronJob(jobRunnable, embeddedCronSchedulerProvider, "test", "* * * * * *");
+        job.schedule();
+        // it can take up to 1000ms until first fire (depending on start time of JUnit test)
+        // as job runs 1500ms the second fire has to be cancelled
+        // the third fire has to run again 
+        // a potential 4th fire (with 3000ms) has too be cancelled (as third is still running)
+        // => exact two executions
+        Thread.sleep(3000);
+        LOG.info("Test Assert {}", simpleDateFormat.format(new Date()));
+        assertEquals("A job running "+callCostInMs+"ms in one execution fired every second can only be executed twice", 2, count.get());
+
     }
+
+
 }
\ No newline at end of file
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronTriggerTest.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronTriggerTest.java
deleted file mode 100644
index ce18fac..0000000
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedCronTriggerTest.java
+++ /dev/null
@@ -1,718 +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 SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.TimeZone;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class EmbeddedCronTriggerTest {
-
-    private final Calendar calendar = new GregorianCalendar();
-
-    private final Date date;
-
-    private final TimeZone timeZone;
-
-    public EmbeddedCronTriggerTest(final Date date, final TimeZone timeZone) {
-        this.date = date;
-        this.timeZone = timeZone;
-    }
-
-    @Parameters(name = "date [{0}], time zone [{1}]")
-    public static List<Object[]> getParameters() {
-        final List<Object[]> list = new ArrayList<>();
-        list.add(new Object[] { new Date(), TimeZone.getTimeZone("PST") });
-        list.add(new Object[] { new Date(), TimeZone.getTimeZone("CET") });
-        return list;
-    }
-
-    private static void roundup(final Calendar calendar) {
-        calendar.add(Calendar.SECOND, 1);
-        calendar.set(Calendar.MILLISECOND, 0);
-    }
-
-    @Before
-    public void setUp() {
-        calendar.setTimeZone(timeZone);
-        calendar.setTime(date);
-        roundup(calendar);
-    }
-
-    @Test
-    public void testMatchAll() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * *", timeZone);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testMatchLastSecond() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * *", timeZone);
-        final GregorianCalendar calendar = new GregorianCalendar();
-        calendar.set(Calendar.SECOND, 58);
-        assertMatchesNextSecond(trigger, calendar);
-    }
-
-    @Test
-    public void testMatchSpecificSecond() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("10 * * * * *", timeZone);
-        final GregorianCalendar calendar = new GregorianCalendar();
-        calendar.set(Calendar.SECOND, 9);
-        assertMatchesNextSecond(trigger, calendar);
-    }
-
-    @Test
-    public void testIncrementSecondByOne() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("11 * * * * *", timeZone);
-        calendar.set(Calendar.SECOND, 10);
-        final Date date = calendar.getTime();
-        calendar.add(Calendar.SECOND, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementSecondWithPreviousExecutionTooEarly() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("11 * * * * *", timeZone);
-        calendar.set(Calendar.SECOND, 11);
-        final Date date = calendar.getTime();
-
-        calendar.add(Calendar.MINUTE, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementSecondAndRollover() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("10 * * * * *", timeZone);
-        calendar.set(Calendar.SECOND, 11);
-        final Date date = calendar.getTime();
-        calendar.add(Calendar.SECOND, 59);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testSecondRange() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("10-15 * * * * *", timeZone);
-        calendar.set(Calendar.SECOND, 9);
-        assertMatchesNextSecond(trigger, calendar);
-        calendar.set(Calendar.SECOND, 14);
-        assertMatchesNextSecond(trigger, calendar);
-    }
-
-    @Test
-    public void testIncrementMinute() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 * * * * *", timeZone);
-        calendar.set(Calendar.MINUTE, 10);
-        Date date = calendar.getTime();
-        calendar.add(Calendar.MINUTE, 1);
-        calendar.set(Calendar.SECOND, 0);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        calendar.add(Calendar.MINUTE, 1);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-    }
-
-    @Test
-    public void testIncrementMinuteByOne() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 11 * * * *", timeZone);
-        calendar.set(Calendar.MINUTE, 10);
-        final Date date = calendar.getTime();
-
-        calendar.add(Calendar.MINUTE, 1);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementMinuteAndRollover() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 10 * * * *", timeZone);
-        calendar.set(Calendar.MINUTE, 11);
-        calendar.set(Calendar.SECOND, 0);
-        final Date date = calendar.getTime();
-        calendar.add(Calendar.MINUTE, 59);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementHour() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 * * * *", timeZone);
-        calendar.set(Calendar.MONTH, 9);
-        calendar.set(Calendar.DAY_OF_MONTH, 30);
-        calendar.set(Calendar.HOUR_OF_DAY, 11);
-        calendar.set(Calendar.MINUTE, 1);
-        calendar.set(Calendar.SECOND, 0);
-        Date date = calendar.getTime();
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.HOUR_OF_DAY, 12);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        calendar.set(Calendar.HOUR_OF_DAY, 13);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementHourAndRollover() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 * * * *", timeZone);
-        calendar.set(Calendar.MONTH, 9);
-        calendar.set(Calendar.DAY_OF_MONTH, 10);
-        calendar.set(Calendar.HOUR_OF_DAY, 23);
-        calendar.set(Calendar.MINUTE, 1);
-        calendar.set(Calendar.SECOND, 0);
-        Date date = calendar.getTime();
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.DAY_OF_MONTH, 11);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        calendar.set(Calendar.HOUR_OF_DAY, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementDayOfMonth() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
-        calendar.set(Calendar.DAY_OF_MONTH, 1);
-        Date date = calendar.getTime();
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        assertEquals(2, calendar.get(Calendar.DAY_OF_MONTH));
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        assertEquals(3, calendar.get(Calendar.DAY_OF_MONTH));
-    }
-
-    @Test
-    public void testIncrementDayOfMonthByOne() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * 10 * *", timeZone);
-        calendar.set(Calendar.DAY_OF_MONTH, 9);
-        final Date date = calendar.getTime();
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementDayOfMonthAndRollover() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * 10 * *", timeZone);
-        calendar.set(Calendar.DAY_OF_MONTH, 11);
-        final Date date = calendar.getTime();
-        calendar.add(Calendar.MONTH, 1);
-        calendar.set(Calendar.DAY_OF_MONTH, 10);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testDailyTriggerInShortMonth() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
-        calendar.set(Calendar.MONTH, 8); // September: 30 days
-        calendar.set(Calendar.DAY_OF_MONTH, 30);
-        Date date = calendar.getTime();
-        calendar.set(Calendar.MONTH, 9); // October
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.DAY_OF_MONTH, 1);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.set(Calendar.DAY_OF_MONTH, 2);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testDailyTriggerInLongMonth() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
-        calendar.set(Calendar.MONTH, 7); // August: 31 days and not a daylight saving boundary
-        calendar.set(Calendar.DAY_OF_MONTH, 30);
-        Date date = calendar.getTime();
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.DAY_OF_MONTH, 31);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.set(Calendar.MONTH, 8); // September
-        calendar.set(Calendar.DAY_OF_MONTH, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testDailyTriggerOnDaylightSavingBoundary() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 * * *", timeZone);
-        calendar.set(Calendar.MONTH, 9); // October: 31 days and a daylight saving boundary in CET
-        calendar.set(Calendar.DAY_OF_MONTH, 30);
-        Date date = calendar.getTime();
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.DAY_OF_MONTH, 31);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.set(Calendar.MONTH, 10); // November
-        calendar.set(Calendar.DAY_OF_MONTH, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementMonth() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 1 * *", timeZone);
-        calendar.set(Calendar.MONTH, 9);
-        calendar.set(Calendar.DAY_OF_MONTH, 30);
-        Date date = calendar.getTime();
-        calendar.set(Calendar.DAY_OF_MONTH, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.MONTH, 10);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.set(Calendar.MONTH, 11);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementMonthAndRollover() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 1 * *", timeZone);
-        calendar.set(Calendar.MONTH, 11);
-        calendar.set(Calendar.DAY_OF_MONTH, 31);
-        calendar.set(Calendar.YEAR, 2010);
-        Date date = calendar.getTime();
-        calendar.set(Calendar.DAY_OF_MONTH, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.set(Calendar.MONTH, 0);
-        calendar.set(Calendar.YEAR, 2011);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.set(Calendar.MONTH, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testMonthlyTriggerInLongMonth() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 31 * *", timeZone);
-        calendar.set(Calendar.MONTH, 9);
-        calendar.set(Calendar.DAY_OF_MONTH, 30);
-        final Date date = calendar.getTime();
-        calendar.set(Calendar.DAY_OF_MONTH, 31);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testMonthlyTriggerInShortMonth() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 1 * *", timeZone);
-        calendar.set(Calendar.MONTH, 9);
-        calendar.set(Calendar.DAY_OF_MONTH, 30);
-        final Date date = calendar.getTime();
-        calendar.set(Calendar.MONTH, 10);
-        calendar.set(Calendar.DAY_OF_MONTH, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testIncrementDayOfWeekByOne() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * 2", timeZone);
-        calendar.set(Calendar.DAY_OF_WEEK, 2);
-        final Date date = calendar.getTime();
-        calendar.add(Calendar.DAY_OF_WEEK, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-        assertEquals(Calendar.TUESDAY, calendar.get(Calendar.DAY_OF_WEEK));
-    }
-
-    @Test
-    public void testIncrementDayOfWeekAndRollover() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * * * 2", timeZone);
-        calendar.set(Calendar.DAY_OF_WEEK, 4);
-        final Date date = calendar.getTime();
-        calendar.add(Calendar.DAY_OF_MONTH, 6);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-        assertEquals(Calendar.TUESDAY, calendar.get(Calendar.DAY_OF_WEEK));
-    }
-
-    @Test
-    public void testSpecificMinuteSecond() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("55 5 * * * *", timeZone);
-        calendar.set(Calendar.MINUTE, 4);
-        calendar.set(Calendar.SECOND, 54);
-        Date date = calendar.getTime();
-
-        calendar.add(Calendar.MINUTE, 1);
-        calendar.set(Calendar.SECOND, 55);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.add(Calendar.HOUR, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testSpecificHourSecond() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("55 * 10 * * *", timeZone);
-        calendar.set(Calendar.HOUR_OF_DAY, 9);
-        calendar.set(Calendar.SECOND, 54);
-        Date date = calendar.getTime();
-
-        calendar.add(Calendar.HOUR_OF_DAY, 1);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 55);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.add(Calendar.MINUTE, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testSpecificMinuteHour() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* 5 10 * * *", timeZone);
-        calendar.set(Calendar.MINUTE, 4);
-        calendar.set(Calendar.HOUR_OF_DAY, 9);
-        Date date = calendar.getTime();
-        calendar.add(Calendar.MINUTE, 1);
-        calendar.add(Calendar.HOUR_OF_DAY, 1);
-        calendar.set(Calendar.SECOND, 0);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        // next trigger is in one second because second is wildcard
-        calendar.add(Calendar.SECOND, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testSpecificDayOfMonthSecond() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("55 * * 3 * *", timeZone);
-        calendar.set(Calendar.DAY_OF_MONTH, 2);
-        calendar.set(Calendar.SECOND, 54);
-        Date date = calendar.getTime();
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 55);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.add(Calendar.MINUTE, 1);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    @Test
-    public void testSpecificDate() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("* * * 3 11 *", timeZone);
-        calendar.set(Calendar.DAY_OF_MONTH, 2);
-        calendar.set(Calendar.MONTH, 9);
-        Date date = calendar.getTime();
-
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MONTH, 10); // 10=November
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-
-        calendar.add(Calendar.SECOND, 1);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testNonExistentSpecificDate() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 31 6 *", timeZone);
-        calendar.set(Calendar.DAY_OF_MONTH, 10);
-        calendar.set(Calendar.MONTH, 2);
-        final Date date = calendar.getTime();
-        trigger.next(date.getTime());
-    }
-
-    @Test
-    public void testLeapYearSpecificDate() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 0 29 2 *", timeZone);
-        calendar.set(Calendar.YEAR, 2007);
-        calendar.set(Calendar.DAY_OF_MONTH, 10);
-        calendar.set(Calendar.MONTH, 1); // 2=February
-        Date date = calendar.getTime();
-
-        calendar.set(Calendar.YEAR, 2008);
-        calendar.set(Calendar.DAY_OF_MONTH, 29);
-        calendar.set(Calendar.HOUR_OF_DAY, 0);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        calendar.add(Calendar.YEAR, 4);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-    }
-
-    @Test
-    public void testWeekDaySequence() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 0 7 ? * MON-FRI", timeZone);
-        // This is a Saturday
-        calendar.set(2009, Calendar.SEPTEMBER, 26);
-        Date date = calendar.getTime();
-        // 7 am is the trigger time
-        calendar.set(Calendar.HOUR_OF_DAY, 7);
-        calendar.set(Calendar.MINUTE, 0);
-        calendar.set(Calendar.SECOND, 0);
-        // Add two days because we start on Saturday
-        calendar.add(Calendar.DAY_OF_MONTH, 2);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        // Next day is a week day so add one
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTimeInMillis(), date.getTime());
-    }
-
-    @Test
-    public void testDayOfWeekIndifferent() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * 2 * *", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * 2 * ?", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testSecondIncrementer() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("57,59 * * * * *", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("57/2 * * * * *", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testSecondIncrementerWithRange() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("1,3,5 * * * * *", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("1-6/2 * * * * *", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testHourIncrementer() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * 4,8,12,16,20 * * *", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * 4/4 * * *", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testDayNames() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * * 0-6", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * * TUE,WED,THU,FRI,SAT,SUN,MON", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testSundayIsZero() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * * 0", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * * SUN", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testSundaySynonym() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * * 0", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * * 7", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testMonthNames() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * 1-12 *", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser(
-                "* * * * FEB,JAN,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC *", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testMonthNamesMixedCase() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("* * * * 2 *", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * Feb *", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testSecondInvalid() {
-        new EmbeddedCronParser("77 * * * * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testSecondRangeInvalid() {
-        new EmbeddedCronParser("44-77 * * * * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testMinuteInvalid() {
-        new EmbeddedCronParser("* 77 * * * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testMinuteRangeInvalid() {
-        new EmbeddedCronParser("* 44-77 * * * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testHourInvalid() {
-        new EmbeddedCronParser("* * 27 * * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testHourRangeInvalid() {
-        new EmbeddedCronParser("* * 23-28 * * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testDayInvalid() {
-        new EmbeddedCronParser("* * * 45 * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testDayRangeInvalid() {
-        new EmbeddedCronParser("* * * 28-45 * *", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testMonthInvalid() {
-        new EmbeddedCronParser("0 0 0 25 13 ?", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testMonthInvalidTooSmall() {
-        new EmbeddedCronParser("0 0 0 25 0 ?", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testDayOfMonthInvalid() {
-        new EmbeddedCronParser("0 0 0 32 12 ?", timeZone);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testMonthRangeInvalid() {
-        new EmbeddedCronParser("* * * * 11-13 *", timeZone);
-    }
-
-    @Test
-    public void testWhitespace() {
-        final EmbeddedCronParser trigger1 = new EmbeddedCronParser("*  *  * *  1 *", timeZone);
-        final EmbeddedCronParser trigger2 = new EmbeddedCronParser("* * * * 1 *", timeZone);
-        assertEquals(trigger1, trigger2);
-    }
-
-    @Test
-    public void testMonthSequence() {
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 30 23 30 1/3 ?", timeZone);
-        calendar.set(2010, Calendar.DECEMBER, 30);
-        Date date = calendar.getTime();
-        // set expected next trigger time
-        calendar.set(Calendar.HOUR_OF_DAY, 23);
-        calendar.set(Calendar.MINUTE, 30);
-        calendar.set(Calendar.SECOND, 0);
-        calendar.add(Calendar.MONTH, 1);
-
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTime(), date);
-
-        // Next trigger is 3 months latter
-        calendar.add(Calendar.MONTH, 3);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTime(), date);
-
-        // Next trigger is 3 months latter
-        calendar.add(Calendar.MONTH, 3);
-        date = new Date(trigger.next(date.getTime()));
-        assertEquals(calendar.getTime(), date);
-    }
-
-    @Test
-    public void testDaylightSavingMissingHour() {
-        // This trigger has to be somewhere in between 2am and 3am
-        final EmbeddedCronParser trigger = new EmbeddedCronParser("0 10 2 * * *", timeZone);
-        calendar.set(Calendar.DAY_OF_MONTH, 31);
-        calendar.set(Calendar.MONTH, Calendar.MARCH);
-        calendar.set(Calendar.YEAR, 2013);
-        calendar.set(Calendar.HOUR_OF_DAY, 1);
-        calendar.set(Calendar.SECOND, 54);
-        final Date date = calendar.getTime();
-        if (timeZone.equals(TimeZone.getTimeZone("CET"))) {
-            // Clocks go forward an hour so 2am doesn't exist in CET for this date
-            calendar.add(Calendar.DAY_OF_MONTH, 1);
-        }
-        calendar.add(Calendar.HOUR_OF_DAY, 1);
-        calendar.set(Calendar.MINUTE, 10);
-        calendar.set(Calendar.SECOND, 0);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-    private void assertMatchesNextSecond(final EmbeddedCronParser trigger, final Calendar calendar) {
-        final Date date = calendar.getTime();
-        roundup(calendar);
-        assertEquals(calendar.getTimeInMillis(), trigger.next(date.getTime()));
-    }
-
-}
\ No newline at end of file
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedSampleCronJob1.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedSampleCronJob1.java
deleted file mode 100644
index 271a0b5..0000000
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedSampleCronJob1.java
+++ /dev/null
@@ -1,60 +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 SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
-
-public class EmbeddedSampleCronJob1 implements EmbeddedCronJob {
-
-    public static volatile int staticCounter = 0;
-    public volatile int counter = 0;
-
-    private final String name;
-    private final Callback callback;
-
-    public EmbeddedSampleCronJob1(final Callback callback) {
-        this.callback = callback;
-        name = "SampleEmbeddedCronJob1";
-    }
-
-    public EmbeddedSampleCronJob1(final Callback callback, final String name) {
-        this.callback = callback;
-        this.name = name;
-    }
-
-    @Override
-    public void run() throws Exception {
-        synchronized (EmbeddedSampleCronJob1.class) {
-            counter++;
-            staticCounter++;
-        }
-        callback.callback(this);
-    }
-
-    public interface Callback {
-        void callback(EmbeddedSampleCronJob1 service);
-    }
-
-    @Override
-    public String cron() {
-        return "* * * * * *";
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-}
\ No newline at end of file
diff --git a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedSampleCronJob2.java b/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedSampleCronJob2.java
deleted file mode 100644
index 42deea3..0000000
--- a/healthcheck/core/src/test/java/org/apache/felix/hc/core/impl/scheduling/cron/embedded/EmbeddedSampleCronJob2.java
+++ /dev/null
@@ -1,29 +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 SF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-package org.apache.felix.hc.core.impl.scheduling.cron.embedded;
-
-public class EmbeddedSampleCronJob2 extends EmbeddedSampleCronJob1 {
-    public EmbeddedSampleCronJob2(final Callback callback) {
-        super(callback);
-    }
-
-    @Override
-    public String name() {
-        return "SampleEmbeddedCronJob2";
-    }
-}
\ No newline at end of file