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