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

cgarcia pushed a commit to branch feature/app
in repository https://gitbox.apache.org/repos/asf/plc4x-extras.git


The following commit(s) were added to refs/heads/feature/app by this push:
     new 7c2ff13  Restructuring the project.
7c2ff13 is described below

commit 7c2ff134cf834e5642b697bcc4da7098e766df45
Author: César García <[email protected]>
AuthorDate: Wed Feb 18 10:28:41 2026 -0400

    Restructuring the project.
---
 .../malbec/malbec-core/core-scheduler/pom.xml      |  62 ++++
 .../plc4x/malbec/core/scheduler/api/Job.java       |  34 +++
 .../malbec/core/scheduler/api/JobContext.java      |  38 +++
 .../malbec/core/scheduler/api/ScheduleOptions.java |  63 ++++
 .../plc4x/malbec/core/scheduler/api/Scheduler.java | 160 ++++++++++
 .../malbec/core/scheduler/api/SchedulerError.java  |  35 +++
 .../malbec/core/scheduler/api/SchedulerMBean.java  |  30 ++
 .../core/scheduler/api/SchedulerStorage.java       |  46 +++
 .../malbec/core/scheduler/core/Activator.java      |  74 +++++
 .../scheduler/core/InternalScheduleOptions.java    | 165 ++++++++++
 .../core/scheduler/core/KarafStdScheduler.java     | 106 +++++++
 .../scheduler/core/KarafStdSchedulerFactory.java   |  45 +++
 .../core/NonParallelQuartzJobExecutor.java         |  31 ++
 .../core/scheduler/core/QuartzJobExecutor.java     |  94 ++++++
 .../core/scheduler/core/QuartzScheduler.java       | 332 +++++++++++++++++++++
 .../scheduler/core/QuartzSchedulerStorage.java     |  50 ++++
 .../core/scheduler/core/SchedulerMBeanImpl.java    |  91 ++++++
 .../malbec/core/scheduler/core/TriggerJob.java     |  47 +++
 .../core/scheduler/core/WhiteboardHandler.java     | 186 ++++++++++++
 .../core-scheduler/src/main/nbm/manifest.mf        |   3 +
 .../plc4x/malbec/core/scheduler/Bundle.properties  |   6 +
 plc4j/tools/malbec/malbec-core/pom.xml             |   1 +
 22 files changed, 1699 insertions(+)

diff --git a/plc4j/tools/malbec/malbec-core/core-scheduler/pom.xml 
b/plc4j/tools/malbec/malbec-core/core-scheduler/pom.xml
new file mode 100644
index 0000000..860dc0d
--- /dev/null
+++ b/plc4j/tools/malbec/malbec-core/core-scheduler/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.plc4x.malbec.core</groupId>
+        <artifactId>malbec-core</artifactId>
+        <version>0.13.0-SNAPSHOT</version>
+    </parent>
+    <groupId>org.apache.plc4x.malbec.core.scheduler</groupId>
+    <artifactId>core-scheduler</artifactId>
+    <packaging>nbm</packaging>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.netbeans.utilities</groupId>
+                <artifactId>nbm-maven-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.netbeans.api</groupId>
+            <artifactId>org-netbeans-api-annotations-common</artifactId>
+            <version>${netbeans.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+            <version>2.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.xml.bind</groupId>
+            <artifactId>jakarta.xml.bind-api</artifactId>
+            <version>4.0.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jaxb</groupId>
+            <artifactId>jaxb-runtime</artifactId>
+            <version>4.0.6</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.activation</groupId>
+            <artifactId>activation</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>2.0.17</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/Job.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/Job.java
new file mode 100644
index 0000000..bd6b42d
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/Job.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.api;
+
+/**
+ * A job is executed by the {@link Scheduler} service.
+ * If the implementation of the job requires certain environment information
+ * it can implement this interface to get additional information
+ * through the provided {@link JobContext}.
+ * If no additional information is required, implementing {@link Runnable} is
+ * sufficient.
+ */
+public interface Job {
+
+    /**
+     * Execute this job.
+     * @param context The context of the job.
+     */
+    void execute(JobContext context);
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/JobContext.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/JobContext.java
new file mode 100644
index 0000000..66235dc
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/JobContext.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.api;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * The context for a {@link Job}.
+ */
+public interface JobContext {
+
+    /**
+     * Get the name of the scheduled job.
+     * @return The name of the job.
+     */
+    String getName();
+
+    /**
+     * Get the configuration provided when the job was scheduled.
+     * @return A non-null map of values.
+     */
+    Map<String, Serializable> getConfiguration();
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/ScheduleOptions.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/ScheduleOptions.java
new file mode 100644
index 0000000..143708c
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/ScheduleOptions.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.api;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * Scheduler options provide an extensible way of defining how to schedule a 
job.
+ * An option can be created via the scheduler.
+ *
+ * @since 2.3
+ */
+public interface ScheduleOptions extends Serializable {
+
+    /**
+     * Add optional configuration for the job.
+     *
+     * @param config An optional configuration object - this configuration is 
only passed to the job the job implements {@link Job}.
+     * @return The {@code ScheduleOptions}.
+     */
+    ScheduleOptions config(final Map<String, Serializable> config);
+
+    /**
+     * Sets the name of the job.
+     * A job only needs a name if it is scheduled and should be cancelled 
later on. The name can then be used to cancel the job.
+     * If a second job with the same name is started, the second one replaces 
the first one.
+     *
+     * @param name The job name.
+     * @return The {@code ScheduleOptions}.
+     */
+    ScheduleOptions name(final String name);
+
+    /**
+     * Flag indicating whether the job can be run concurrently.
+     * This defaults to false.
+     *
+     * @param flag Whether this job can run even if previous scheduled runs 
are still running.
+     * @return The {@code ScheduleOptions}.
+     */
+    ScheduleOptions canRunConcurrently(final boolean flag);
+
+    String name();
+
+    boolean canRunConcurrently();
+
+    String schedule();
+
+}
\ No newline at end of file
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/Scheduler.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/Scheduler.java
new file mode 100644
index 0000000..0f8d2a7
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/Scheduler.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.api;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * A scheduler to schedule time/cron based jobs.
+ * A job is an object that is executed/fired by the scheduler. The object
+ * should either implement the {@link Job} interface or the {@link Runnable}
+ * interface.
+ *
+ * A job can be scheduled either by creating a {@link ScheduleOptions} instance
+ * through one of the scheduler methods and then calling {@link 
#schedule(Object, ScheduleOptions)}
+ * or
+ * by using the whiteboard pattern and registering a Runnable service with 
either
+ * the {@link #PROPERTY_SCHEDULER_EXPRESSION} or {@link 
#PROPERTY_SCHEDULER_PERIOD}
+ * property. Services registered by the whiteboard pattern can by default run 
concurrently,
+ * which usually is not wanted. Therefore it is advisable to also set the
+ * {@link #PROPERTY_SCHEDULER_CONCURRENT} property with Boolean.FALSE.
+ */
+public interface Scheduler {
+
+    /**
+     * Name of the configuration property to define the period for a job.
+     * The period is expressed in seconds.
+     * This property needs to be of type Long.
+     */
+    String PROPERTY_SCHEDULER_PERIOD = "scheduler.period";
+
+    /**
+     * Name of the configuration property to defined the number of iterations 
for a job.
+     * The times is expressed in iterations.
+     * This property needs to be of numeric type.
+     */
+    String PROPERTY_SCHEDULER_TIMES = "scheduler.times";
+
+    /**
+     * Name of the configuration property to define if a periodically job 
should be scheduled immediate.
+     * Default is to not startup immediate, the job is started the first time 
after the period has expired.
+     * This property needs to be of type Boolean.
+     */
+    String PROPERTY_SCHEDULER_IMMEDIATE = "scheduler.immediate";
+
+    /** Name of the configuration property to define the cron expression for a 
job. */
+    String PROPERTY_SCHEDULER_EXPRESSION = "scheduler.expression";
+
+    /** Name of the configuration property to define if the job can be run 
concurrently. */
+    String PROPERTY_SCHEDULER_CONCURRENT = "scheduler.concurrent";
+
+    /** Name of the configuration property to define the job name. */
+    String PROPERTY_SCHEDULER_NAME = "scheduler.name";
+
+
+    /**
+     * Schedule a job based on the options.
+     *
+     * Note that if a job with the same name has already been added, the old 
job is cancelled and this new job replaces
+     * the old job.
+     *
+     * The job object needs either to be a {@link Job} or a {@link Runnable}. 
The options have to be created
+     * by one of the provided methods from this scheduler.
+     *
+     * @param job The job to execute (either {@link Job} or {@link Runnable}).
+     * @param options Required options defining how to schedule the job.
+     * @throws SchedulerError if the job can't be scheduled.
+     * @throws IllegalArgumentException If the preconditions are not met.
+     * @see #NOW()
+     * @see #NOW(int, long)
+     * @see #AT(Date)
+     * @see #AT(Date, int, long)
+     * @see #EXPR(String)
+     */
+    void schedule(Object job, ScheduleOptions options) throws 
IllegalArgumentException, SchedulerError;
+
+    /**
+     * Update the scheduling of an existing job.
+     * @param jobName
+     * @param options
+     * @throws IllegalArgumentException
+     * @throws SchedulerError
+     */
+    void reschedule(String jobName, ScheduleOptions options) throws 
IllegalArgumentException, SchedulerError;
+
+    /**
+     * Remove a scheduled job by name.
+     *
+     * @param jobName The name of the job.
+     * @return <code>True</code> if the job existed and could be stopped, 
<code>false</code> otherwise.
+     */
+    boolean unschedule(String jobName);
+
+    Map<String, ScheduleOptions> getJobs() throws SchedulerError;
+
+    /**
+     * Triggers a scheduled job.
+     *
+     * @param jobName The name of the job.
+     * @return <code>true</code> if the job was triggered, otherwise 
<code>false</code>
+     * @throws SchedulerError  if the job can't be triggered.
+     */
+    boolean trigger(String jobName) throws SchedulerError;
+
+    /**
+     * Create a schedule options to fire a job immediately and only once.
+     *
+     * @return The corresponding {@link ScheduleOptions}.
+     */
+    ScheduleOptions NOW();
+
+    /**
+     * Create a schedule options to fire a job immediately more than once.
+     * @param times The number of times this job should be started (must be 
higher than 1 or -1 for endless).
+     * @param period Every period seconds this job is started (must be at 
higher than 0).
+     * @return The corresponding {@link ScheduleOptions}.
+     */
+    ScheduleOptions NOW(int times, long period);
+
+    /**
+     * Create a schedule options to fire a job once at a specific date.
+     *
+     * @param date The date this job should be run.
+     * @return The corresponding {@link ScheduleOptions}.
+     */
+    ScheduleOptions AT(final Date date);
+
+    /**
+     * Create a schedule options to fire a job period starting at a specific 
date.
+     *
+     * @param date The date this job should be run.
+     * @param times The number of times this job should be started (must be 
higher than 1 or -1 for endless).
+     * @param period Every period seconds this job is started (must be at 
higher than 0).
+     * @return The corresponding {@link ScheduleOptions}.
+     */
+    ScheduleOptions AT(final Date date, int times, long period);
+
+    /**
+     * Create a schedule options to schedule the job based on the expression.
+     *
+     * @param expression The cron exception.
+     * @return The corresponding {@link ScheduleOptions}.
+     */
+    ScheduleOptions EXPR(final String expression);
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerError.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerError.java
new file mode 100644
index 0000000..2d986f3
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerError.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 The Apache Software Foundation.
+ *
+ * Licensed 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.plc4x.malbec.core.scheduler.api;
+
+public class SchedulerError extends Exception {
+
+    public SchedulerError() {
+    }
+
+    public SchedulerError(String msg) {
+        super(msg);
+    }
+
+    public SchedulerError(Throwable cause) {
+        super(cause);
+    }
+
+    public SchedulerError(String msg, Throwable cause) {
+        super(msg, cause);
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerMBean.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerMBean.java
new file mode 100644
index 0000000..c500cf5
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerMBean.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.api;
+
+import javax.management.MBeanException;
+import javax.management.openmbean.TabularData;
+
+public interface SchedulerMBean {
+
+    TabularData getJobs() throws MBeanException;
+
+    void trigger(String name, boolean background) throws MBeanException;
+
+    void unschedule(String name) throws MBeanException;
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerStorage.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerStorage.java
new file mode 100644
index 0000000..ac2dc52
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/api/SchedulerStorage.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.api;
+
+import java.io.Serializable;
+
+/**
+ * A job storage definition. It's easily extensible to match user needs.
+ */
+public interface SchedulerStorage {
+
+    /**
+     * Retrieve a job from the store.
+     */
+    <T> T get(final Serializable key);
+
+    /**
+     * Add a job in the store.
+     */
+    void put(final Serializable key, final Object value);
+
+    /**
+     * Check if the job exists in the store.
+     */
+    boolean contains(final Serializable key);
+
+    /**
+     * Release a job from the store.
+     */
+    void release(final Serializable key);
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/Activator.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/Activator.java
new file mode 100644
index 0000000..1f18b99
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/Activator.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.scheduler.core;
+
+import org.apache.karaf.scheduler.Scheduler;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.Managed;
+import org.apache.karaf.util.tracker.annotation.ProvideService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.service.cm.ManagedService;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+@Services(provides = @ProvideService(Scheduler.class))
+@Managed("org.apache.karaf.scheduler.quartz")
+public class Activator extends BaseActivator implements ManagedService {
+
+    private QuartzScheduler scheduler;
+    private WhiteboardHandler whiteboardHandler;
+
+    @Override
+    protected void doStart() throws Exception {
+        Properties properties = new Properties();
+        if (getConfiguration() == null) {
+            return;
+        }
+        Enumeration<String> keys = getConfiguration().keys();
+        while (keys.hasMoreElements()) {
+            String key = keys.nextElement();
+            if (key.startsWith("org.quartz")) {
+                Object value = getConfiguration().get(key);
+                properties.put(key, value);
+            }
+        }
+        scheduler = new QuartzScheduler(properties);
+        register(Scheduler.class, scheduler);
+        whiteboardHandler = new WhiteboardHandler(bundleContext, scheduler);
+
+        SchedulerMBeanImpl mBean = new SchedulerMBeanImpl();
+        mBean.setScheduler(scheduler);
+        registerMBean(mBean, "type=scheduler");
+    }
+
+    @Override
+    protected void doStop() {
+        super.doStop();
+
+        if (whiteboardHandler != null) {
+            whiteboardHandler.deactivate();
+            whiteboardHandler = null;
+        }
+        if (scheduler != null) {
+            scheduler.deactivate();
+            scheduler = null;
+        }
+        super.doStop();
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/InternalScheduleOptions.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/InternalScheduleOptions.java
new file mode 100644
index 0000000..e078b07
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/InternalScheduleOptions.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import jakarta.xml.bind.DatatypeConverter;
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Map;
+
+import org.apache.plc4x.malbec.core.scheduler.api.ScheduleOptions;
+import org.quartz.CronExpression;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.SimpleScheduleBuilder;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
+
+/**
+ * Scheduler options provide an extensible way of defining how to schedule a 
job.
+ * @since 2.3
+ */
+public class InternalScheduleOptions implements ScheduleOptions {
+
+    private static final long serialVersionUID = -2632689849349264449L;
+
+    public String name;
+
+    public boolean canRunConcurrently = false;
+
+    public Map<String, Serializable> configuration;
+
+    public String schedule;
+
+    private Date date;
+    private int times;
+    private long period;
+    private String expression;
+
+    public InternalScheduleOptions(Date date) {
+        this.date = date;
+        this.times = 0;
+        this.period = 0;
+        this.schedule = null;
+        this.expression = null;
+    }
+
+    public InternalScheduleOptions(Date date, int times, long period) {
+        this.date = date;
+        this.times = times;
+        this.period = period;
+        this.schedule = null;
+        this.expression = null;
+    }
+
+    public InternalScheduleOptions(String expression) {
+        this.date = null;
+        this.times = 0;
+        this.period = 0;
+        this.schedule = null;
+        this.expression = expression;
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.ScheduleOptions#config(java.util.Map)
+     */
+    public ScheduleOptions config(final Map<String, Serializable> config) {
+        this.configuration = config;
+        return this;
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.ScheduleOptions#name(java.lang.String)
+     */
+    public ScheduleOptions name(final String name) {
+        this.name = name;
+        return this;
+    }
+
+    /**
+     * @see 
org.apache.karaf.scheduler.ScheduleOptions#canRunConcurrently(boolean)
+     */
+    public ScheduleOptions canRunConcurrently(final boolean flag) {
+        this.canRunConcurrently = flag;
+        return this;
+    }
+
+    @Override
+    public String name() {
+        return this.name;
+    }
+
+    @Override
+    public boolean canRunConcurrently() {
+        return this.canRunConcurrently;
+    }
+
+    @Override
+    public String schedule() {
+        return schedule;
+    }
+
+    private String formatDate(Date date) {
+        if (date == null) {
+            return "null";
+        }
+        Calendar c = GregorianCalendar.getInstance();
+        c.setTime(date);
+        return DatatypeConverter.printDateTime(c);
+    }
+
+    public TriggerBuilder<? extends Trigger> compile() {
+        TriggerBuilder<? extends Trigger> trigger = null;
+        if (expression == null) {
+            if (date == null) {
+                throw new IllegalArgumentException("Date can't be null");
+            } else {
+                boolean dateOnly = false;
+                if (times < 2 && times != -1) {
+                    dateOnly = true;
+                }
+                if (period < 1) {
+                    dateOnly = true;
+                }
+                if (dateOnly) {
+                    trigger = TriggerBuilder.newTrigger().startAt(date);
+                    this.schedule = "at(" + formatDate(date) + ")";
+                } else {
+                    final SimpleScheduleBuilder simpleScheduleBuilder;
+                    if (times == -1) {
+                        simpleScheduleBuilder = 
SimpleScheduleBuilder.simpleSchedule().repeatForever();
+                    } else {
+                        simpleScheduleBuilder = 
SimpleScheduleBuilder.simpleSchedule().withRepeatCount(times - 1);
+                    }
+                    trigger = TriggerBuilder.newTrigger()
+                            .startAt(date)
+                            
.withSchedule(simpleScheduleBuilder.withIntervalInMilliseconds(period * 1000));
+                    this.schedule = "at(" + formatDate(date) + ", " + times + 
", " + period + ")";
+                }
+            }
+        } else {
+            if (!CronExpression.isValidExpression(expression)) {
+                throw new IllegalArgumentException("Expression is not valid: " 
+ expression);
+            }
+            trigger = 
TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule(expression));
+            this.schedule = "cron(" + expression + ")";
+        }
+        return trigger;
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/KarafStdScheduler.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/KarafStdScheduler.java
new file mode 100644
index 0000000..2542291
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/KarafStdScheduler.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerKey;
+import org.quartz.impl.StdScheduler;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class KarafStdScheduler extends StdScheduler {
+
+    private final QuartzSchedulerStorage storage;
+
+    public KarafStdScheduler(final org.quartz.core.QuartzScheduler scheduler) {
+        super(scheduler);
+        this.storage = new QuartzSchedulerStorage();
+    }
+
+    QuartzSchedulerStorage getStorage() {
+        return this.storage;
+    }
+
+    @Override
+    public Date scheduleJob(final JobDetail jobDetail, final Trigger trigger) 
throws SchedulerException {
+        JobDataMap context = (JobDataMap) 
jobDetail.getJobDataMap().get(QuartzScheduler.DATA_MAP_CONTEXT);
+        storage.put(jobDetail.getKey().toString(), context);
+
+        jobDetail.getJobDataMap().remove(QuartzScheduler.DATA_MAP_CONTEXT);
+
+        final Date date = super.scheduleJob(jobDetail, trigger);
+        return date;
+    }
+
+    @Override
+    public boolean deleteJob(JobKey jobKey) throws SchedulerException {
+        final String contextKey = jobKey.toString();
+        if (contextKey != null) {
+            storage.release(contextKey);
+        }
+        return super.deleteJob(jobKey);
+    }
+
+    @Override
+    public boolean deleteJobs(List<JobKey> jobKeys) throws SchedulerException {
+        if (jobKeys != null) {
+            final List<String> contextKeys = new ArrayList<>();
+            for (JobKey jobKey : jobKeys) {
+                contextKeys.add(jobKey.toString());
+            }
+            for (String contextKey : contextKeys) {
+                storage.release(contextKey);
+            }
+        }
+        return super.deleteJobs(jobKeys);
+    }
+
+    @Override
+    public boolean unscheduleJob(TriggerKey triggerKey) throws 
SchedulerException {
+        final Trigger trigger = getTrigger(triggerKey);
+        final String contextKey = (trigger.getJobKey() != null) ? 
trigger.getJobKey().toString() : null;
+        if (contextKey != null) {
+            storage.release(contextKey);
+        }
+        return super.unscheduleJob(triggerKey);
+    }
+
+    @Override
+    public boolean unscheduleJobs(List<TriggerKey> triggerKeys) throws 
SchedulerException {
+        if (triggerKeys != null) {
+            final List<String> contextKeys = new ArrayList<>();
+            for (TriggerKey triggerKey : triggerKeys) {
+                final Trigger trigger = getTrigger(triggerKey);
+                final String contextKey = trigger.getJobKey().toString();
+                if (contextKey != null) {
+                    contextKeys.add(contextKey);
+                }
+            }
+            for (String contextKey : contextKeys) {
+                storage.release(contextKey);
+            }
+        }
+        return super.unscheduleJobs(triggerKeys);
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/KarafStdSchedulerFactory.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/KarafStdSchedulerFactory.java
new file mode 100644
index 0000000..750550b
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/KarafStdSchedulerFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.core.QuartzSchedulerResources;
+import org.quartz.impl.StdSchedulerFactory;
+
+import java.util.Properties;
+
+public class KarafStdSchedulerFactory extends StdSchedulerFactory {
+
+    public KarafStdSchedulerFactory() {
+        throw new IllegalStateException("Not supported. Use: 
org.apache.karaf.scheduler.core.KarafStdSchedulerFactory(java.util.Properties)");
+    }
+
+    public KarafStdSchedulerFactory(final Properties properties) throws 
SchedulerException {
+        super(properties);
+    }
+
+    public KarafStdSchedulerFactory(final String fileName) throws 
SchedulerException {
+        throw new IllegalStateException("Not supported. Use: 
org.apache.karaf.scheduler.core.KarafStdSchedulerFactory(java.util.Properties)");
+    }
+
+    public Scheduler instantiate(final QuartzSchedulerResources resources, 
final org.quartz.core.QuartzScheduler quartzScheduler) {
+        final Scheduler scheduler = new KarafStdScheduler(quartzScheduler);
+        return scheduler;
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/NonParallelQuartzJobExecutor.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/NonParallelQuartzJobExecutor.java
new file mode 100644
index 0000000..201b89c
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/NonParallelQuartzJobExecutor.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import org.quartz.DisallowConcurrentExecution;
+
+
+/**
+ * This component is responsible to launch a {@link 
org.apache.karaf.scheduler.Job}
+ * or {@link Runnable} in a Quartz Scheduler but non concurrently.
+ *
+ */
+@DisallowConcurrentExecution
+public class NonParallelQuartzJobExecutor extends QuartzJobExecutor {
+
+    // nothing to code here
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzJobExecutor.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzJobExecutor.java
new file mode 100644
index 0000000..ec8cb8d
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzJobExecutor.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import java.io.Serializable;
+import java.util.Map;
+import org.apache.plc4x.malbec.core.scheduler.api.JobContext;
+
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This component is responsible to launch a {@link 
org.apache.karaf.scheduler.Job}
+ * or {@link Runnable} in a Quartz Scheduler.
+ *
+ */
+public class QuartzJobExecutor implements Job {
+
+    private final static Logger LOGGER = 
LoggerFactory.getLogger(QuartzJobExecutor.class);
+
+    /**
+     * @see org.quartz.Job#execute(org.quartz.JobExecutionContext)
+     */
+    public void execute(final JobExecutionContext context) throws 
JobExecutionException {
+
+        final KarafStdScheduler scheduler = (KarafStdScheduler) 
context.getScheduler();
+        final JobDataMap data = context.getJobDetail().getJobDataMap();
+        final String contextKey = (context.getJobDetail().getKey() != null) ? 
context.getJobDetail().getKey().toString() : null;
+        final JobDataMap karafContext = (contextKey != null) ? 
scheduler.getStorage().get(contextKey) : null;
+        final Object job = (karafContext != null) ? 
karafContext.get(QuartzScheduler.DATA_MAP_OBJECT) : context.getJobInstance();
+        final Logger logger = (karafContext != null) ? (Logger) 
karafContext.get(QuartzScheduler.DATA_MAP_LOGGER) : LOGGER;
+
+        try {
+            logger.debug("Executing job {} with name {}", job, 
data.get(QuartzScheduler.DATA_MAP_NAME));
+            if (job instanceof org.apache.plc4x.malbec.core.scheduler.api.Job) 
{
+                final InternalScheduleOptions options = 
(InternalScheduleOptions) data.get(QuartzScheduler.DATA_MAP_OPTIONS);
+                final String name = (String) 
data.get(QuartzScheduler.DATA_MAP_NAME);
+
+                final JobContext jobCtx = new JobContextImpl(name, 
options.configuration);
+                ((org.apache.plc4x.malbec.core.scheduler.api.Job) 
job).execute(jobCtx);
+            } else if (job instanceof Runnable) {
+                ((Runnable) job).run();
+            } else {
+                logger.error("Scheduled job {} is neither a job nor a 
runnable.", job);
+            }
+        } catch (final Throwable t) {
+            // there is nothing we can do here, so we just log
+            logger.error("Exception during job execution of " + job + " : " + 
t.getMessage(), t);
+        }
+    }
+
+    public static final class JobContextImpl implements JobContext {
+
+        protected final Map<String, Serializable> configuration;
+        protected final String name;
+
+        public JobContextImpl(String name, Map<String, Serializable> config) {
+            this.name = name;
+            this.configuration = config;
+        }
+
+        /**
+         * @see org.apache.karaf.scheduler.JobContext#getConfiguration()
+         */
+        public Map<String, Serializable> getConfiguration() {
+            return this.configuration;
+        }
+
+        /**
+         * @see org.apache.karaf.scheduler.JobContext#getName()
+         */
+        public String getName() {
+            return this.name;
+        }
+    }
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzScheduler.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzScheduler.java
new file mode 100644
index 0000000..107704b
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzScheduler.java
@@ -0,0 +1,332 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import java.util.*;
+import org.apache.plc4x.malbec.core.scheduler.api.ScheduleOptions;
+import org.apache.plc4x.malbec.core.scheduler.api.Scheduler;
+import org.apache.plc4x.malbec.core.scheduler.api.SchedulerError;
+
+
+import org.quartz.*;
+import org.quartz.impl.matchers.GroupMatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The quartz based implementation of the scheduler.
+ *
+ */
+public class QuartzScheduler implements Scheduler {
+
+    /** Default logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private static final String PREFIX = "Apache Karaf Quartz Scheduler ";
+
+    /** Map key for the job object */
+    static final String DATA_MAP_OBJECT = "QuartzJobScheduler.Object";
+
+    /** Map key for the job name */
+    static final String DATA_MAP_NAME = "QuartzJobScheduler.JobName";
+
+    /** Map key for the scheduling options. */
+    static final String DATA_MAP_OPTIONS = "QuartzJobScheduler.Options";
+
+    /** Map key for non serializable context. */
+    static final String DATA_MAP_CONTEXT = "QuartzJobScheduler.Context";
+
+    /** Map key for the logger. */
+    static final String DATA_MAP_LOGGER = "QuartzJobScheduler.Logger";
+
+    /** The quartz scheduler. */
+    private volatile org.quartz.Scheduler scheduler;
+
+    public QuartzScheduler(Properties configuration) {
+        // SLING-2261 Prevent Quartz from checking for updates
+        System.setProperty("org.terracotta.quartz.skipUpdateCheck", 
Boolean.TRUE.toString());
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        try {
+            
Thread.currentThread().setContextClassLoader(QuartzScheduler.class.getClassLoader());
+            KarafStdSchedulerFactory factory = new 
KarafStdSchedulerFactory(configuration);
+            scheduler = factory.getScheduler();
+            scheduler.start();
+        } catch (Throwable t) {
+            throw new RuntimeException("Unable to create quartz scheduler", t);
+        } finally {
+            Thread.currentThread().setContextClassLoader(cl);
+        }
+    }
+
+    /**
+     * Deactivate this component.
+     * Stop the scheduler.
+     */
+    public void deactivate() {
+        final org.quartz.Scheduler s = this.scheduler;
+        this.scheduler = null;
+        this.dispose(s);
+    }
+
+    /**
+     * Dispose the quartz scheduler
+     * @param s The scheduler.
+     */
+    private void dispose(final org.quartz.Scheduler s) {
+        if ( s != null ) {
+            try {
+                s.shutdown();
+            } catch (SchedulerException e) {
+                this.logger.debug("Exception during shutdown of scheduler.", 
e);
+            }
+            if ( this.logger.isDebugEnabled() ) {
+                this.logger.debug(PREFIX + "stopped.");
+            }
+        }
+    }
+
+    /**
+     * Initialize the data map for the job executor.
+     */
+    private JobDataMap initDataMap(final String  jobName,
+                                   final Object  job,
+                                   final InternalScheduleOptions options) {
+        final JobDataMap jobDataMap = new JobDataMap();
+        final JobDataMap jobContextMap = new JobDataMap();
+
+        // serializable data
+        jobDataMap.put(DATA_MAP_NAME, jobName);
+        jobDataMap.put(DATA_MAP_OPTIONS, options);
+
+        // non serializable data
+        jobContextMap.put(DATA_MAP_OBJECT, job);
+        jobContextMap.put(DATA_MAP_LOGGER, this.logger);
+
+        // temporary storage
+        jobDataMap.put(DATA_MAP_CONTEXT, jobContextMap);
+
+        return jobDataMap;
+    }
+
+    /**
+     * Create the job detail.
+     */
+    private JobDetail createJobDetail(final String name,
+                                      final JobDataMap jobDataMap,
+                                      final boolean concurrent) {
+        return JobBuilder.newJob((concurrent ? QuartzJobExecutor.class : 
NonParallelQuartzJobExecutor.class))
+                .withIdentity(name)
+                .usingJobData(jobDataMap)
+                .build();
+    }
+
+    /**
+     * Check the job object, either runnable or job is allowed
+     */
+    private void checkJob(final Object job)
+            throws IllegalArgumentException {
+        if (!(job instanceof Runnable) && !(job instanceof Job)) {
+            throw new IllegalArgumentException("Job object is neither an 
instance of " + Runnable.class.getName() + " nor " + Job.class.getName());
+        }
+    }
+
+    /** Used by the web console plugin. */
+    org.quartz.Scheduler getScheduler() {
+        return this.scheduler;
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#NOW()
+     */
+    public ScheduleOptions NOW() {
+        return AT(new Date());
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#NOW(int, long)
+     */
+    public ScheduleOptions NOW(int times, long period) {
+        return AT(new Date(), times, period);
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#AT(java.util.Date)
+     */
+    public ScheduleOptions AT(Date date) {
+        return new InternalScheduleOptions(date);
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#AT(java.util.Date, int, long)
+     */
+    public ScheduleOptions AT(Date date, int times, long period) {
+        return new InternalScheduleOptions(date, times, period);
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#EXPR(java.lang.String)
+     */
+    public ScheduleOptions EXPR(String expression) {
+        return new InternalScheduleOptions(expression);
+    }
+
+    /**
+     * Schedule a job
+     * @see org.apache.karaf.scheduler.Scheduler#schedule(java.lang.Object, 
org.apache.karaf.scheduler.ScheduleOptions)
+     * @throws SchedulerError if the job can't be scheduled
+     * @throws IllegalArgumentException If the preconditions are not met
+     */
+    public void schedule(final Object job, final ScheduleOptions options) 
throws IllegalArgumentException, SchedulerError {
+        this.checkJob(job);
+
+        if ( !(options instanceof InternalScheduleOptions)) {
+            throw new IllegalArgumentException("Options has not been created 
via schedule or is null.");
+        }
+        final InternalScheduleOptions opts = (InternalScheduleOptions)options;
+
+        // as this method might be called from unbind and during
+        // unbind a deactivate could happen, we check the scheduler first
+        final org.quartz.Scheduler s = this.scheduler;
+        if ( s == null ) {
+            throw new IllegalStateException("Scheduler is not available 
anymore.");
+        }
+
+        final String name;
+        if ( opts.name != null ) {
+            // if there is already a job with the name, remove it first
+            try {
+                final JobKey key = JobKey.jobKey(opts.name);
+                final JobDetail jobdetail = s.getJobDetail(key);
+                if (jobdetail != null) {
+                    s.deleteJob(key);
+                    this.logger.debug("Unscheduling job with name {}", 
opts.name);
+                }
+            } catch (final SchedulerException ignored) {
+                // ignore
+            }
+            name = opts.name;
+        } else {
+            name = job.getClass().getName() + ':' + UUID.randomUUID();
+            opts.name = name;
+        }
+
+        final Trigger trigger = opts.compile().withIdentity(name).build();
+
+        // create the data map
+        final JobDataMap jobDataMap = this.initDataMap(name, job, opts);
+
+        final JobDetail detail = this.createJobDetail(name, jobDataMap, 
opts.canRunConcurrently);
+
+        this.logger.debug("Scheduling job {} with name {} and trigger {}", 
job, name, trigger);
+        try {
+            s.scheduleJob(detail, trigger);
+        } catch (SchedulerException ex) {
+            throw new SchedulerError(ex);
+        }
+    }
+
+    @Override
+    public void reschedule(String jobName, ScheduleOptions options) throws 
SchedulerError {
+        final org.quartz.Scheduler s = this.scheduler;
+        if (jobName == null) {
+            throw new IllegalArgumentException("Job name is mandatory");
+        }
+        JobKey key = JobKey.jobKey(jobName);
+        if (key == null) {
+            throw new IllegalStateException("No job found with name " + 
jobName);
+        }
+        try {
+            JobDetail detail = s.getJobDetail(key);
+
+            final String contextKey = key.toString();
+            JobDataMap karafContext = ((KarafStdScheduler) 
s).getStorage().get(contextKey);
+            Object job = karafContext.get(QuartzScheduler.DATA_MAP_OBJECT);
+
+            s.deleteJob(key);
+
+            final InternalScheduleOptions opts = 
(InternalScheduleOptions)options;
+            Trigger trigger = opts.compile().withIdentity(jobName).build();
+            JobDataMap jobDataMap = this.initDataMap(jobName, job, opts);
+            detail = createJobDetail(jobName, jobDataMap, 
opts.canRunConcurrently);
+
+            logger.debug("Update job scheduling {} with name {} and trigger 
{}", job, jobName, trigger);
+            s.scheduleJob(detail, trigger);
+        } catch (SchedulerException e) {
+            throw new SchedulerError(e);
+        }
+    }
+
+    /**
+     * @see org.apache.karaf.scheduler.Scheduler#unschedule(java.lang.String)
+     */
+    public boolean unschedule(final String jobName) {
+        final org.quartz.Scheduler s = this.scheduler;
+        if (jobName != null && s != null) {
+            try {
+                final JobKey key = JobKey.jobKey(jobName);
+                final JobDetail jobdetail = s.getJobDetail(key);
+                if (jobdetail != null) {
+                    s.deleteJob(key);
+                    this.logger.debug("Unscheduling job with name {}", 
jobName);
+                    return true;
+                }
+            } catch (final SchedulerException ignored) {
+                // ignore
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Map<String, ScheduleOptions> getJobs() throws SchedulerError {
+        try {
+            Map<String, ScheduleOptions> jobs = new HashMap<>();
+            org.quartz.Scheduler s = this.scheduler;
+            if (s != null) {
+                for (String group : s.getJobGroupNames()) {
+                    for (JobKey key : 
s.getJobKeys(GroupMatcher.jobGroupEquals(group))) {
+                        JobDetail detail = s.getJobDetail(key);
+                        ScheduleOptions options = (ScheduleOptions) 
detail.getJobDataMap().get(DATA_MAP_OPTIONS);
+                        jobs.put(key.getName(), options);
+                    }
+                }
+            }
+            return jobs;
+        } catch (SchedulerException ex) {
+            throw new SchedulerError(ex);
+        }
+    }
+
+    @Override
+    public boolean trigger(String jobName) throws SchedulerError {
+        final org.quartz.Scheduler s = this.scheduler;
+        if (jobName != null && s != null) {
+            try {
+                final JobKey key = JobKey.jobKey(jobName);
+                final JobDetail jobdetail = s.getJobDetail(key);
+                if (jobdetail != null) {
+                    this.scheduler.triggerJob(key, jobdetail.getJobDataMap());
+                    return true;
+                }
+            } catch (SchedulerException ex) {
+                throw new SchedulerError(ex);
+            }
+        }
+        return false;
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzSchedulerStorage.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzSchedulerStorage.java
new file mode 100644
index 0000000..e30a1a3
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/QuartzSchedulerStorage.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.plc4x.malbec.core.scheduler.api.SchedulerStorage;
+
+public class QuartzSchedulerStorage implements SchedulerStorage {
+
+    private final Map<Serializable, Object> store = new HashMap<>();
+
+    @Override
+    public <T> T get(Serializable key) {
+        return (T) this.store.get(key);
+    }
+
+    @Override
+    public void put(Serializable key, Object value) {
+        this.store.put(key, value);
+    }
+
+    @Override
+    public boolean contains(Serializable key) {
+        return this.store.containsKey(key);
+    }
+
+    @Override
+    public void release(Serializable key) {
+        this.store.remove(key);
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/SchedulerMBeanImpl.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/SchedulerMBeanImpl.java
new file mode 100644
index 0000000..9dea66e
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/SchedulerMBeanImpl.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+
+
+import javax.management.MBeanException;
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import javax.management.openmbean.*;
+import java.util.Map;
+import org.apache.plc4x.malbec.core.scheduler.api.ScheduleOptions;
+import org.apache.plc4x.malbec.core.scheduler.api.SchedulerMBean;
+import org.apache.plc4x.malbec.core.scheduler.api.Scheduler;
+
+public class SchedulerMBeanImpl extends StandardMBean implements 
SchedulerMBean {
+
+    private Scheduler scheduler;
+
+    public SchedulerMBeanImpl() throws NotCompliantMBeanException {
+        super(SchedulerMBean.class);
+    }
+
+    @Override
+    public TabularData getJobs() throws MBeanException {
+        try {
+            CompositeType jobType = new CompositeType("Job", "Scheduler job",
+                    new String[]{ "Job", "Schedule" },
+                    new String[]{ "Job Name", "Job Scheduling" },
+                    new OpenType[]{ SimpleType.STRING, SimpleType.STRING });
+            TabularType tableType = new TabularType("Jobs", "Tables of all 
jobs", jobType, new String[]{ "Job" });
+            TabularData table = new TabularDataSupport(tableType);
+
+            Map<String, ScheduleOptions> jobs = scheduler.getJobs();
+            for (Map.Entry<String, ScheduleOptions> entry : jobs.entrySet()) {
+                CompositeData data = new CompositeDataSupport(jobType,
+                        new String[]{ "Job", "Schedule" },
+                        new Object[]{ entry.getKey(), 
entry.getValue().schedule()});
+                table.put(data);
+            }
+            return table;
+        } catch (Exception e) {
+            throw new MBeanException(null, e.toString());
+        }
+    }
+
+    @Override
+    public void trigger(String name, boolean background) throws MBeanException 
{
+        try {
+            if (background) {
+                scheduler.schedule(new TriggerJob(scheduler, name), 
scheduler.NOW());
+            } else {
+                scheduler.trigger(name);
+            }
+        } catch (Exception e) {
+            throw new MBeanException(null, e.toString());
+        }
+    }
+
+    @Override
+    public void unschedule(String name) throws MBeanException {
+        try {
+            scheduler.unschedule(name);
+        } catch (Exception e) {
+            throw new MBeanException(null, e.toString());
+        }
+    }
+
+    public Scheduler getScheduler() {
+        return scheduler;
+    }
+
+    public void setScheduler(Scheduler scheduler) {
+        this.scheduler = scheduler;
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/TriggerJob.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/TriggerJob.java
new file mode 100644
index 0000000..fd8633a
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/TriggerJob.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import org.apache.plc4x.malbec.core.scheduler.api.SchedulerError;
+import org.apache.plc4x.malbec.core.scheduler.api.Scheduler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TriggerJob implements Runnable {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(TriggerJob.class);
+
+    private final Scheduler scheduler;
+    private final String name;
+
+    public TriggerJob(Scheduler scheduler, String name) {
+        this.scheduler = scheduler;
+        this.name = name;
+    }
+
+    @Override
+    public void run() {
+        try {
+            if (!scheduler.trigger(name)) {
+                LOGGER.warn("Could not find a scheduled job with name " + 
name);
+            }
+        } catch (SchedulerError ex) {
+            LOGGER.error("Failed to trigger job {}", name, ex);
+        }
+    }
+
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/WhiteboardHandler.java
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/WhiteboardHandler.java
new file mode 100644
index 0000000..f39a99a
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/java/org/apache/plc4x/malbec/core/scheduler/core/WhiteboardHandler.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.plc4x.malbec.core.scheduler.core;
+
+import java.lang.System.Logger;
+import java.util.ArrayList;
+import java.util.Date;
+
+
+import org.apache.plc4x.malbec.core.scheduler.api.Scheduler;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.quartz.Job;
+import org.quartz.impl.jdbcjobstore.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The quartz based implementation of the scheduler.
+ *
+ */
+public class WhiteboardHandler {
+
+    /** Default logger. */
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private Scheduler scheduler;
+
+    private ServiceTracker<?,?> serviceTracker;
+
+    public WhiteboardHandler(final BundleContext context, Scheduler scheduler) 
throws InvalidSyntaxException {
+        this.scheduler = scheduler;
+        this.serviceTracker = new ServiceTracker<>(context,
+                context.createFilter("(|(" + Constants.OBJECTCLASS + "=" + 
Runnable.class.getName() + ")" +
+                        "(" + Constants.OBJECTCLASS + "=" + 
Job.class.getName() + "))"),
+                new ServiceTrackerCustomizer<Object,Object>() {
+
+                    public synchronized void  removedService(final 
ServiceReference reference, final Object service) {
+                        context.ungetService(reference);
+                        unregister(reference, service);
+                    }
+
+                    public synchronized void modifiedService(final 
ServiceReference reference, final Object service) {
+                        unregister(reference, service);
+                        register(reference, service);
+                    }
+
+                    public synchronized Object addingService(final 
ServiceReference reference) {
+                        final Object obj = context.getService(reference);
+                        if ( obj != null ) {
+                            register(reference, obj);
+                        }
+                        return obj;
+                    }
+                });
+        this.serviceTracker.open();
+    }
+
+    /**
+     * Deactivate this component.
+     */
+    public void deactivate() {
+        this.serviceTracker.close();
+    }
+
+
+    /**
+     * Create unique identifier
+     */
+    private String getServiceIdentifier(final ServiceReference ref) {
+        String name = (String) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_NAME);
+        if ( name == null ) {
+            if (ref.getProperty(Constants.SERVICE_PID) instanceof String) {
+                name = (String) ref.getProperty(Constants.SERVICE_PID);
+            } else if (ref.getProperty(Constants.SERVICE_PID) instanceof 
ArrayList) {
+                if (((ArrayList) 
ref.getProperty(Constants.SERVICE_PID)).size() > 0) {
+                    name = ((ArrayList) 
ref.getProperty(Constants.SERVICE_PID)).get(0).toString();
+                }
+            }
+            if (name == null) {
+                name = "Registered Service";
+            }
+        }
+        // now append service id to create a unique identifier
+        name = name + "." + ref.getProperty(Constants.SERVICE_ID);
+        return name;
+    }
+
+    /**
+     * Register a job or task
+     */
+    private void register(final ServiceReference ref, final Object job) {
+        final String name = getServiceIdentifier(ref);
+        Boolean concurrent = true;
+        if (ref.getProperty(Scheduler.PROPERTY_SCHEDULER_CONCURRENT) != null) {
+            if (ref.getProperty(Scheduler.PROPERTY_SCHEDULER_CONCURRENT) 
instanceof Boolean) {
+                concurrent = (Boolean) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_CONCURRENT);
+            } else {
+                concurrent = Boolean.valueOf((String) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_CONCURRENT));
+            }
+        }
+        final String expression = (String) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_EXPRESSION);
+        try {
+            if (expression != null) {
+                this.scheduler.schedule(job, this.scheduler.EXPR(expression)
+                        .name(name)
+                        .canRunConcurrently(concurrent));
+            } else {
+                Integer times = -1;
+                {
+                    final Object v = 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_TIMES);
+                    if (null != v) {
+                        if (v instanceof Integer) {
+                            times = (Integer) v;
+                        } else if (v instanceof Long) {
+                            times = ((Long) v).intValue();
+                        } else if (v instanceof Number) {
+                            times = ((Number) v).intValue();
+                        } else {
+                            times = Integer.valueOf(v.toString());
+                        }
+                    }
+                }
+
+                Long period = null;
+                if (ref.getProperty(Scheduler.PROPERTY_SCHEDULER_PERIOD) != 
null) {
+                    if (ref.getProperty(Scheduler.PROPERTY_SCHEDULER_PERIOD) 
instanceof Long) {
+                        period = (Long) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_PERIOD);
+                    } else {
+                        period = Long.valueOf((String) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_PERIOD));
+                    }
+                    if (period < 1) {
+                        this.logger.debug("Ignoring service {} : scheduler 
period is less than 1.", ref);
+                    } else if (times < -1) {
+                        this.logger.debug("Ignoring service {} : scheduler 
times is defined but is less than -1.", ref);
+                    } else {
+                        boolean immediate = false;
+                        if 
(ref.getProperty(Scheduler.PROPERTY_SCHEDULER_IMMEDIATE) != null) {
+                            if 
(ref.getProperty(Scheduler.PROPERTY_SCHEDULER_IMMEDIATE) instanceof Boolean) {
+                                immediate = (Boolean) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_IMMEDIATE);
+                            } else {
+                                immediate = Boolean.valueOf((String) 
ref.getProperty(Scheduler.PROPERTY_SCHEDULER_IMMEDIATE));
+                            }
+                        }
+                        final Date date = new Date();
+                        if (!immediate) {
+                            date.setTime(System.currentTimeMillis() + period * 
1000);
+                        }
+                        this.scheduler.schedule(job, this.scheduler.AT(date, 
times, period)
+                                .name(name)
+                                .canRunConcurrently((concurrent != null ? 
concurrent : true)));
+                    }
+                } else {
+                    this.logger.debug("Ignoring service {} : no scheduling 
property found.", ref);
+                }
+            }
+        } catch (Exception e) {
+            logger.warn("Error scheduling job", e);
+        }
+    }
+
+    /**
+     * Unregister a service.
+     */
+    private void unregister(final ServiceReference reference, final Object 
service) {
+        final String name = getServiceIdentifier(reference);
+        this.scheduler.unschedule(name);
+    }
+}
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/nbm/manifest.mf 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/nbm/manifest.mf
new file mode 100644
index 0000000..d47d5a1
--- /dev/null
+++ b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/nbm/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+OpenIDE-Module-Localizing-Bundle: 
org/apache/plc4x/malbec/core/scheduler/Bundle.properties
+
diff --git 
a/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/resources/org/apache/plc4x/malbec/core/scheduler/Bundle.properties
 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/resources/org/apache/plc4x/malbec/core/scheduler/Bundle.properties
new file mode 100644
index 0000000..756a322
--- /dev/null
+++ 
b/plc4j/tools/malbec/malbec-core/core-scheduler/src/main/resources/org/apache/plc4x/malbec/core/scheduler/Bundle.properties
@@ -0,0 +1,6 @@
+#Localized module labels. Defaults taken from POM (<name>, <description>, 
<groupId>) if unset.
+#OpenIDE-Module-Name=
+#OpenIDE-Module-Short-Description=
+#OpenIDE-Module-Long-Description=
+#OpenIDE-Module-Display-Category=
+#Tue Feb 17 21:18:53 GMT-04:00 2026
diff --git a/plc4j/tools/malbec/malbec-core/pom.xml 
b/plc4j/tools/malbec/malbec-core/pom.xml
index 05b4a0c..a628574 100644
--- a/plc4j/tools/malbec/malbec-core/pom.xml
+++ b/plc4j/tools/malbec/malbec-core/pom.xml
@@ -11,5 +11,6 @@
     <packaging>pom</packaging>
     <modules>
         <module>core-css</module>
+        <module>core-scheduler</module>
     </modules>
 </project>
\ No newline at end of file

Reply via email to