diff --git a/biz.aQute.api/bnd.bnd b/biz.aQute.api/bnd.bnd index 841b3793..f6893200 100644 --- a/biz.aQute.api/bnd.bnd +++ b/biz.aQute.api/bnd.bnd @@ -10,6 +10,7 @@ org.osgi.dto,\ org.apache.felix.http.servlet-api,\ org.osgi.service.component.annotations,\ - org.osgi.namespace.extender + org.osgi.namespace.extender,\ + org.osgi.service.metatype.annotations -sub: *.bnd \ No newline at end of file diff --git a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/Constants.java b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/Constants.java new file mode 100644 index 00000000..966b3807 --- /dev/null +++ b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/Constants.java @@ -0,0 +1,83 @@ +package biz.aQute.scheduler.api; + +public final class Constants { + + private Constants() { + // Constants + } + + /** + * Specification Name + */ + public static final String SPECIFICATION_NAME = "aqute.scheduler"; + + /** + * Specification Version + */ + public static final String SPECIFICATION_VERSION = "1.1.0"; + + /** + * The service property Prefix for cronjobs + */ + public static final String SERVICE_PROPERTY_CRONJOB_PREFIX = "cronjob"; + + /** + * The service property that specifies the cron schedule. The type is String+. + */ + public static final String SERVICE_PROPERTY_CRONJOB_CRON = SERVICE_PROPERTY_CRONJOB_PREFIX+".cron"; + + /** + * The service property that specifies the name of the cron job. The type is + * String. + */ + public static final String SERVICE_PROPERTY_CRONJOB_NAME = SERVICE_PROPERTY_CRONJOB_PREFIX+".name"; + + /** + * Default name of the cron job. + */ + public static final String CRONJOB_NAME_DEFAULT = "unknown"; + + /** + * the named cron expression for annually execution. + */ + public static final String CRON_EXPRESSION_ANNUALLY = "@annually"; + + /** + * the named cron expression for yearly execution. + */ + public static final String CRON_EXPRESSION_YEARLY = "@yearly"; + + /** + * the named cron expression for monthly execution. + */ + public static final String CRON_EXPRESSION_MONTHLY = "@monthly"; + /** + * the named cron expression for weekly execution. + */ + public static final String CRON_EXPRESSION_WEEKLY = "@weekly"; + + /** + * the named cron expression for daily execution. + */ + public static final String CRON_EXPRESSION_DAYLY = "@daily"; + + /** + * the named cron expression for hourly execution. + */ + public static final String CRON_EXPRESSION_HOURLY = "@hourly"; + + /** + * the named cron expression for minutely execution. + */ + public static final String CRON_EXPRESSION_MINUTLY = "@minutely"; + + /** + * the named cron expression for secondly execution. + */ + public static final String CRON_EXPRESSION_SECUNDLY = "@secondly"; + + /** + * the named cron expression for execution onreboot. + */ + public static final String CRON_EXPRESSION_REBOOT = "@reboot"; +} diff --git a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronExpression.java b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronExpression.java index e6ce78b7..0baa3b5e 100644 --- a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronExpression.java +++ b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronExpression.java @@ -1,18 +1,32 @@ package biz.aQute.scheduler.api; import org.osgi.service.component.annotations.ComponentPropertyType; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; /** * An annotation to simplify using a CronJob */ @ComponentPropertyType +@ObjectClassDefinition +@RequireSchedulerImplementation public @interface CronExpression { + public static final String PREFIX_ = Constants.SERVICE_PROPERTY_CRONJOB_PREFIX; /** - * The 'cron.expression' service property + * The 'cronjob.cron' service property as defines in {@link Constants#SERVICE_PROPERTY_CRONJOB_CRON} * @return */ - String cron(); + @AttributeDefinition(name="cronJobCronExpression", description = "Cron Expression according the Cron Spec. see http://en.wikipedia.org/wiki/Cron") + String[] cron(); + + /** + * The 'cronjob.name' service property as defines in {@link Constants#SERVIC_PROPERTY_CRON_NAME()()} + * @return + */ + @AttributeDefinition(name="cronJobName", description = "Human readable name of the cronjob") + String name() default Constants.CRONJOB_NAME_DEFAULT; + } diff --git a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronJob.java b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronJob.java index 8f5ca276..5ba6d7b1 100644 --- a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronJob.java +++ b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/CronJob.java @@ -11,7 +11,7 @@ * chronos. *

* The Unix Cron defines a syntax that is used by the Cron service. A user - * should register a Cron service with the {@value CronJob#CRON} property. The + * should register a Cron service with the {@value Constants#SERVICE_PROPERTY_CRONJOB_CRON} property. The * value is according to the {link http://en.wikipedia.org/wiki/Cron}. *

* @@ -77,8 +77,15 @@ public interface CronJob { /** * The service property that specifies the cron schedule. The type is * String+. + * @deprecated use {@link Constants#SERVICE_PROPERTY_CRONJOB_CRON} "cron.expression"; */ + @Deprecated String CRON = "cron"; + + /** + * @deprecated use {@link Constants#SERVICE_PROPERTY_CRONJOB_NAME} "cron.name"; + */ + @Deprecated String NAME = "name"; /** diff --git a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/RequireSchedulerImplementation.java b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/RequireSchedulerImplementation.java new file mode 100644 index 00000000..8503200e --- /dev/null +++ b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/RequireSchedulerImplementation.java @@ -0,0 +1,17 @@ +package biz.aQute.scheduler.api; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.osgi.annotation.bundle.Requirement; +import org.osgi.namespace.implementation.ImplementationNamespace; + +/** + * Require an implementation for the this specification + */ +@Requirement(namespace = ImplementationNamespace.IMPLEMENTATION_NAMESPACE, filter = "(&(" + + ImplementationNamespace.IMPLEMENTATION_NAMESPACE + "=" + + Constants.SPECIFICATION_NAME + ")${frange;${version;==;" + + Constants.SPECIFICATION_VERSION + "}})") +@Retention(RetentionPolicy.CLASS) +public @interface RequireSchedulerImplementation {} \ No newline at end of file diff --git a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/Scheduler.java b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/Scheduler.java index 444b3e12..49f476da 100644 --- a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/Scheduler.java +++ b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/Scheduler.java @@ -1,5 +1,6 @@ package biz.aQute.scheduler.api; +import java.time.Duration; import java.time.temporal.TemporalAdjuster; import java.util.concurrent.Callable; import java.util.concurrent.Executor; @@ -21,12 +22,41 @@ interface RunnableWithException { * Schedule a runnable to run periodically at a fixed rate. The schedule can * be canceled by the returned task. * + * @param runnable + * the task to run + * @param name + * The name of the task + * @param fixedRateInMs + * The fixed rate in milliseconds. + * + */ + Task periodic(Runnable runnable, long fixedRateInMs, String name); + + /** + * Schedule a runnable to run periodically at a fixed rate. The schedule can + * be canceled by the returned task. + * + * @param runnable + * the task to run + * @param name + * The name of the task + * @param fixedRate + * The fixed rate in milliseconds. + * + */ + Task periodic(Runnable runnable, Duration fixedRate, String name); + /** + * Schedule a runnable to run after a certain time. The schedule can be + * canceled by the returned task if it was not yet canceled. + * * @param name * The name of the task * @param runnable * the task to run + * @param afterMs + * time in ms after the run is scheduled. */ - Task periodic(Runnable runnable, long ms, String name); + Task after(Runnable runnable, long afterMs, String name); /** * Schedule a runnable to run after a certain time. The schedule can be @@ -36,8 +66,10 @@ interface RunnableWithException { * The name of the task * @param runnable * the task to run + * @param after + * duration after the run is scheduled. */ - Task after(Runnable runnable, long ms, String name); + Task after(Runnable runnable, Duration after, String name); /** * Executes a task in the background, intended for short term tasks @@ -69,7 +101,7 @@ interface RunnableWithException { /** * Schedule a runnable to be executed for the give cron expression (See - * {@link CronJob}). Every time when the cronExpression matches the current + * {@link CronExpression#expression()}). Every time when the cronExpression matches the current * time, the runnable will be run. The method returns a closeable that can * be used to stop scheduling. This variation does not take an environment * object. @@ -96,7 +128,7 @@ interface RunnableWithException { * ZonedDateTime next = now.with(cron); * * - * @param cronExpression a Cron expression as specified in {@link CronJob} + * @param cronExpression a Cron expression as specified in {@link CronExpression#expression()} * @return a Temporal Adjuster based on a cron expression */ TemporalAdjuster getCronAdjuster(String cronExpression); diff --git a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/package-info.java b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/package-info.java index 84b7d5b2..46074a40 100644 --- a/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/package-info.java +++ b/biz.aQute.api/src/main/java/biz/aQute/scheduler/api/package-info.java @@ -1,5 +1,5 @@ @org.osgi.annotation.bundle.Export -@Version("1.0.0") +@Version(biz.aQute.scheduler.api.Constants.SPECIFICATION_VERSION) package biz.aQute.scheduler.api; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.scheduler.basic.provider/bnd.bnd b/biz.aQute.scheduler.basic.provider/bnd.bnd index 33649075..814d01ec 100644 --- a/biz.aQute.scheduler.basic.provider/bnd.bnd +++ b/biz.aQute.scheduler.basic.provider/bnd.bnd @@ -8,8 +8,9 @@ slf4j.api,\ biz.aQute.api.scheduler,\ org.osgi.util.promise,\ - aQute.libg,\ - org.osgi.service.metatype.annotations + org.osgi.service.metatype.annotations,\ + org.osgi.util.converter,\ + org.osgi.namespace.implementation -testpath: \ @@ -18,8 +19,8 @@ org.assertj.core,\ org.awaitility,\ biz.aQute.launchpad,\ + aQute.libg,\ osgi.core, \ - slf4j.api, \ + org.osgi.util.function,\ + slf4j.api,\ slf4j.simple - --conditionalpackage: aQute.lib* \ No newline at end of file diff --git a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/SchedulerConfig.java b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/SchedulerConfig.java index 16ffad44..710f5bd6 100644 --- a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/SchedulerConfig.java +++ b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/SchedulerConfig.java @@ -1,20 +1,27 @@ package biz.aQute.scheduler.basic.config; +import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.ObjectClassDefinition; -/** - * Configuration for the scheduler. The scheduler is a singleton - */ - -@ObjectClassDefinition +@ObjectClassDefinition( description = "Configuration for the scheduler. The scheduler is a singleton") public @interface SchedulerConfig { String PID = "biz.aQute.scheduler.basic"; String SYSTEM_DEFAULT_TIMEZONE = "system.default.timezone"; + String DESCRIPTION = "Set the time zone. The default is the system default time zone. " + + "If the time zone does not exist, an error is logged and the system default time zone is used."; + int COREPOOLSIZE_DEFAUL = 50; + int SHUTDOWNTIMEOUT_SOFT_DEFAUL = 500; + int SHUTDOWNTIMEOUT_HARD_DEFAUL = 5000; - /** - * Set the time zone. The default is the system default time zone. If the - * time zone does not exist, an error is logged and the system default time zone is - * used. - */ + @AttributeDefinition(description = DESCRIPTION) String timeZone() default SYSTEM_DEFAULT_TIMEZONE; + + @AttributeDefinition(description = "corePoolSize of ScheduledThreadPoolExecutor") + int corePoolSize() default COREPOOLSIZE_DEFAUL; + + @AttributeDefinition(description = "Expected shutdownTimeout of ScheduledThreadPoolExecutor") + int shutdownTimeoutSoft() default SHUTDOWNTIMEOUT_SOFT_DEFAUL; + + @AttributeDefinition(description = "Expected shutdownTimeout after the shutdownTimeoutSoft befor the shutdown of ScheduledThreadPoolExecutor is forced") + int shutdownTimeoutHard() default SHUTDOWNTIMEOUT_HARD_DEFAUL; } \ No newline at end of file diff --git a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/package-info.java b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/package-info.java index 5b3b7cff..b92d316c 100644 --- a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/package-info.java +++ b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/config/package-info.java @@ -1,5 +1,5 @@ @org.osgi.annotation.bundle.Export -@Version("1.0.0") +@Version("1.1.0") package biz.aQute.scheduler.basic.config; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CentralScheduler.java b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CentralScheduler.java index 326b4bf9..8051c2a5 100644 --- a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CentralScheduler.java +++ b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CentralScheduler.java @@ -15,6 +15,8 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.osgi.annotation.bundle.Capability; +import org.osgi.namespace.implementation.ImplementationNamespace; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.ConfigurationPolicy; @@ -25,12 +27,14 @@ import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ServiceScope; import org.osgi.service.metatype.annotations.Designate; +import org.osgi.util.converter.Converter; +import org.osgi.util.converter.Converters; import org.osgi.util.promise.Promise; import org.osgi.util.promise.PromiseFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import aQute.lib.converter.Converter; +import biz.aQute.scheduler.api.Constants; import biz.aQute.scheduler.api.CronJob; import biz.aQute.scheduler.api.Task; import biz.aQute.scheduler.basic.config.SchedulerConfig; @@ -48,19 +52,31 @@ configurationPolicy = ConfigurationPolicy.OPTIONAL, name = SchedulerConfig.PID ) +@Capability( + namespace = ImplementationNamespace.IMPLEMENTATION_NAMESPACE, + name = Constants.SPECIFICATION_NAME, + version = Constants.SPECIFICATION_VERSION +) //@formatter:on public class CentralScheduler { - final List crons = new ArrayList<>(); + + final List scheduledCrons = new ArrayList<>(); final static Logger logger = LoggerFactory.getLogger(SchedulerImpl.class); + final static Converter converter = Converters.standardConverter(); final ScheduledExecutorService scheduler; final PromiseFactory factory; Clock clock = Clock.systemDefaultZone(); - long shutdownTimeout = 5000; + long shutdownTimeoutSoft; + long shutdownTimeoutHard; final SchedulerImpl frameworkTasks = new SchedulerImpl(this); @Activate public CentralScheduler(SchedulerConfig config) { - scheduler = Executors.newScheduledThreadPool(50); + int corePoolSize = config == null ? SchedulerConfig.COREPOOLSIZE_DEFAUL : config.corePoolSize(); + shutdownTimeoutSoft = config == null ? SchedulerConfig.SHUTDOWNTIMEOUT_SOFT_DEFAUL : config.shutdownTimeoutSoft(); + shutdownTimeoutHard = config == null ? SchedulerConfig.SHUTDOWNTIMEOUT_HARD_DEFAUL : config.shutdownTimeoutHard(); + + scheduler = Executors.newScheduledThreadPool(corePoolSize); factory = new PromiseFactory(scheduler); modified(config); } @@ -88,10 +104,11 @@ void deactivate() { frameworkTasks.deactivate(); scheduler.shutdown(); try { - if (scheduler.awaitTermination(500, TimeUnit.MILLISECONDS)) + if (scheduler.awaitTermination(shutdownTimeoutSoft, TimeUnit.MILLISECONDS)) { return; + } logger.info("waiting for scheduler to shutdown"); - scheduler.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS); + scheduler.awaitTermination(shutdownTimeoutHard, TimeUnit.MILLISECONDS); if (!scheduler.isTerminated()) { logger.info("forcing shutdown"); List shutdownNow = scheduler.shutdownNow(); @@ -123,14 +140,14 @@ public Promise submit(Callable callable, String name) { }); } - class Cron { + class ScheduledCronJob { - CronJob target; + CronJob innerCrobJob; Task schedule; - Cron(CronJob target, String cronExpression, String name) throws Exception { - this.target = target; - this.schedule = frameworkTasks.schedule(target::run, cronExpression, name); + ScheduledCronJob(CronJob cronJob, String cronExpression, String name) throws Exception { + this.innerCrobJob = cronJob; + this.schedule = frameworkTasks.schedule(this.innerCrobJob::run, cronExpression, name); } void close() throws IOException { @@ -162,21 +179,34 @@ long nextDelay(CronAdjuster cron) { } @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MULTIPLE) - void addSchedule(CronJob s, Map map) throws Exception { - String name = Converter.cnv(String.class, map.get(CronJob.NAME)); - String[] schedules = Converter.cnv(String[].class, map.get(CronJob.CRON)); - if (schedules == null || schedules.length == 0) - return; + void addSchedule(CronJob cronJob, Map map) throws Exception { + String name = converter.convert(map.get(Constants.SERVICE_PROPERTY_CRONJOB_NAME)).to(String.class); + String nameOld = converter.convert(map.get(CronJob.NAME)).to(String.class); + + String[] schedules = converter.convert(map.get(Constants.SERVICE_PROPERTY_CRONJOB_CRON)).to(String[].class); + String[] schedulesOld = converter.convert(map.get(CronJob.CRON)).to(String[].class); + + if (schedules == null || schedules.length == 0) { + if (schedulesOld == null || schedulesOld.length == 0) { + return; + } + schedules = schedulesOld; + } + if (name == null) { - name = "unknown " + Instant.now(); + if (nameOld == null) { + name = Constants.CRONJOB_NAME_DEFAULT + " " + Instant.now(); + }else { + name = nameOld; + } } - synchronized (crons) { + synchronized (scheduledCrons) { for (String schedule : schedules) { try { - Cron cron = new Cron(s, schedule, name); - crons.add(cron); + ScheduledCronJob cron = new ScheduledCronJob(cronJob, schedule, name); + scheduledCrons.add(cron); } catch (Exception e) { logger.error("Invalid cron expression " + schedule + " from " + map, e); } @@ -184,13 +214,13 @@ void addSchedule(CronJob s, Map map) throws Exception { } } - void removeSchedule(CronJob s) { - synchronized (crons) { - for (Iterator cron = crons.iterator(); cron.hasNext();) { - Cron c = cron.next(); - if (c.target == s) { - cron.remove(); - c.schedule.cancel(); + void removeSchedule(CronJob cronJobToRemove) { + synchronized (scheduledCrons) { + for (Iterator scheduledCronsIterator = scheduledCrons.iterator(); scheduledCronsIterator.hasNext();) { + ScheduledCronJob scheduledCronJob = scheduledCronsIterator.next(); + if (scheduledCronJob.innerCrobJob == cronJobToRemove) { + scheduledCronsIterator.remove(); + scheduledCronJob.schedule.cancel(); } } } diff --git a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CronAdjuster.java b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CronAdjuster.java index 7d31c958..33c60fca 100644 --- a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CronAdjuster.java +++ b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/CronAdjuster.java @@ -9,6 +9,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import biz.aQute.scheduler.api.Constants; + /** * This class creates a Temporal Adjuster that takes a temporal and adjust it to * the next deadline based on a cron specification. @@ -142,29 +144,29 @@ public CronAdjuster(String specification) { private String preDeclared(String expression) { switch (expression) { - case "@annually" : - case "@yearly" : + case Constants.CRON_EXPRESSION_ANNUALLY : + case Constants.CRON_EXPRESSION_YEARLY : return "0 0 0 1 1 *"; - case "@monthly" : + case Constants.CRON_EXPRESSION_MONTHLY : return "1 0 0 1 * *"; - case "@weekly" : + case Constants.CRON_EXPRESSION_WEEKLY : return "2 0 0 ? * MON"; - case "@daily" : + case Constants.CRON_EXPRESSION_DAYLY : return "3 0 0 * * ?"; - case "@hourly" : + case Constants.CRON_EXPRESSION_HOURLY : return "4 0 * * * ?"; - case "@minutely" : + case Constants.CRON_EXPRESSION_MINUTLY : return "* 0 * * * *"; - case "@secondly" : + case Constants.CRON_EXPRESSION_SECUNDLY: return "* * * * * *"; - case "@reboot" : + case Constants.CRON_EXPRESSION_REBOOT : return "0 0 0 1 1 ? 1900"; default : diff --git a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/SchedulerImpl.java b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/SchedulerImpl.java index fc8362ed..69e26d93 100644 --- a/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/SchedulerImpl.java +++ b/biz.aQute.scheduler.basic.provider/src/main/java/biz/aQute/scheduler/basic/provider/SchedulerImpl.java @@ -1,5 +1,6 @@ package biz.aQute.scheduler.basic.provider; +import java.time.Duration; import java.time.Instant; import java.time.temporal.TemporalAdjuster; import java.util.Collections; @@ -127,8 +128,14 @@ void deactivate() { @Override public Task periodic(Runnable runnable, long ms, String name) { + return periodic(runnable, Duration.ofMillis(ms), name); + } + + @Override + public Task periodic(Runnable runnable, Duration duration, String name) { TaskImpl task = new TaskImpl(runnable, name, false); - ScheduledFuture future = scheduler.scheduler.scheduleAtFixedRate(task, ms, ms, TimeUnit.MILLISECONDS); + long nanos = duration.toNanos(); + ScheduledFuture future = scheduler.scheduler.scheduleAtFixedRate(task, nanos, nanos, TimeUnit.NANOSECONDS); task.cancel = () -> future.cancel(true); tasks.add(task); return task; @@ -136,8 +143,15 @@ public Task periodic(Runnable runnable, long ms, String name) { @Override public Task after(Runnable runnable, long ms, String name) { + return after(runnable, Duration.ofMillis(ms), name); + } + + @Override + public Task after(Runnable runnable, Duration duration, String name) { TaskImpl task = new TaskImpl(runnable, name, false); - ScheduledFuture future = scheduler.scheduler.schedule(task, ms, TimeUnit.MILLISECONDS); + long nanos = duration.toNanos(); + + ScheduledFuture future = scheduler.scheduler.schedule(task, nanos, TimeUnit.NANOSECONDS); task.cancel = () -> future.cancel(true); tasks.add(task); return task; diff --git a/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerBundleTest.java b/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerBundleTest.java index fbe54c2f..fffae27c 100644 --- a/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerBundleTest.java +++ b/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerBundleTest.java @@ -1,14 +1,12 @@ package biz.aQute.scheduler.basic.provider; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Optional; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import org.awaitility.Awaitility; import org.junit.Test; import org.osgi.framework.Bundle; import org.osgi.service.component.annotations.Component; @@ -16,10 +14,20 @@ import aQute.launchpad.Launchpad; import aQute.launchpad.LaunchpadBuilder; import biz.aQute.scheduler.api.CronJob; +import biz.aQute.scheduler.api.Constants; public class SchedulerBundleTest { static LaunchpadBuilder builder = new LaunchpadBuilder().bndrun("test.bndrun"); + @Component(property = Constants.SERVICE_PROPERTY_CRONJOB_CRON+"=@reboot") + public static class TestRebootServiceNewServiceProp implements CronJob { + static Semaphore present = new Semaphore(0); + + @Override + public void run() throws Exception { + present.release(); + } + } @Component(property = "cron=@reboot") public static class TestRebootService implements CronJob { @@ -44,7 +52,7 @@ public void run() throws Exception { } @Test - public void testAnnotation() throws Exception { + public void testOldServiceProperty() throws Exception { try (Launchpad lp = builder.create()) { TestRebootService.present.drainPermits(); lp.bundle().addResource(TestRebootService.class).start(); @@ -55,6 +63,19 @@ public void testAnnotation() throws Exception { assertTrue(found); } } + + @Test + public void testNewServiceProperty() throws Exception { + try (Launchpad lp = builder.create()) { + TestRebootServiceNewServiceProp.present.drainPermits(); + lp.bundle().addResource(TestRebootServiceNewServiceProp.class).start(); + Optional service = lp.getService(CronJob.class); + assertThat(service).isPresent(); + + boolean found = TestRebootServiceNewServiceProp.present.tryAcquire(1, 5000, TimeUnit.MILLISECONDS); + assertTrue(found); + } + } @Test public void testCleanedup() throws Exception { diff --git a/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerImplTest.java b/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerImplTest.java index c488b5ab..a1f43708 100644 --- a/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerImplTest.java +++ b/biz.aQute.scheduler.basic.provider/src/test/java/biz/aQute/scheduler/basic/provider/SchedulerImplTest.java @@ -97,7 +97,7 @@ public void testShutdown() throws InterruptedException { AtomicReference thread = new AtomicReference<>(); try { - cs.shutdownTimeout = 100; + cs.shutdownTimeoutHard = 100; impl.execute(() -> { try { diff --git a/biz.aQute.scheduler.basic.provider/test.bndrun b/biz.aQute.scheduler.basic.provider/test.bndrun index 259cb4c7..da9eee9f 100644 --- a/biz.aQute.scheduler.basic.provider/test.bndrun +++ b/biz.aQute.scheduler.basic.provider/test.bndrun @@ -6,6 +6,6 @@ biz.aQute.api.scheduler;version=snapshot,\ biz.aQute.scheduler.basic.provider;version=snapshot,\ org.apache.felix.scr;version='[2.1.30,2.1.31)',\ - aQute.libg;version='[6.4.1,6.4.2)',\ org.osgi.util.function;version='[1.2.0,1.2.1)',\ - org.osgi.util.promise;version='[1.3.0,1.3.1)' \ No newline at end of file + org.osgi.util.promise;version='[1.3.0,1.3.1)',\ + org.osgi.util.converter;version='[1.0.9,1.0.10)' \ No newline at end of file