diff --git a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/pom.xml b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/pom.xml
index c7c29403bb46..c4982d322453 100644
--- a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/pom.xml
+++ b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/pom.xml
@@ -34,7 +34,7 @@
Scheduler API
- 0.08
+ 0.07
@@ -46,6 +46,11 @@
org.quartz-scheduler
quartz
+
+ org.xwiki.commons
+ xwiki-commons-classloader-api
+ ${commons.version}
+
org.xwiki.platform
xwiki-platform-test-oldcore
diff --git a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPlugin.java b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPlugin.java
index 0f7fe23cda1e..058dbad1215e 100644
--- a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPlugin.java
+++ b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/SchedulerPlugin.java
@@ -21,7 +21,6 @@
import java.net.URL;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
@@ -48,8 +47,10 @@
import org.xwiki.bridge.event.DocumentDeletedEvent;
import org.xwiki.bridge.event.DocumentUpdatedEvent;
import org.xwiki.bridge.event.WikiDeletedEvent;
+import org.xwiki.classloader.internal.ClassLoaderResetEvent;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.context.concurrent.ExecutionContextRunnable;
+import org.xwiki.model.namespace.WikiNamespace;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReference;
import org.xwiki.observation.EventListener;
@@ -68,6 +69,7 @@
import com.xpn.xwiki.plugin.scheduler.internal.SchedulerJobClassDocumentInitializer;
import com.xpn.xwiki.plugin.scheduler.internal.SchedulerJobsInitializedEvent;
import com.xpn.xwiki.plugin.scheduler.internal.SchedulerJobsInitializingEvent;
+import com.xpn.xwiki.plugin.scheduler.internal.SchedulersClassLoaderManager;
import com.xpn.xwiki.plugin.scheduler.internal.StatusListener;
import com.xpn.xwiki.web.Utils;
import com.xpn.xwiki.web.XWikiResponse;
@@ -102,8 +104,13 @@ public class SchedulerPlugin extends XWikiDefaultPlugin implements EventListener
public static final EntityReference XWIKI_JOB_CLASSREFERENCE =
SchedulerJobClassDocumentInitializer.XWIKI_JOB_CLASSREFERENCE;
- private static final List EVENTS = Arrays.asList(new DocumentCreatedEvent(),
- new DocumentDeletedEvent(), new DocumentUpdatedEvent(), new WikiDeletedEvent());
+ private static final List EVENTS = List.of(
+ new DocumentCreatedEvent(),
+ new DocumentDeletedEvent(),
+ new DocumentUpdatedEvent(),
+ new WikiDeletedEvent(),
+ new ClassLoaderResetEvent()
+ );
/**
* Default Quartz scheduler instance.
@@ -112,6 +119,8 @@ public class SchedulerPlugin extends XWikiDefaultPlugin implements EventListener
private boolean enabled;
+ private SchedulersClassLoaderManager schedulersClassLoaderManager;
+
/**
* Default plugin constructor.
*
@@ -128,6 +137,8 @@ public void init(XWikiContext context)
// Check if the Scheduler plugin is enabled
this.enabled =
Utils.getComponent(ConfigurationSource.class, "xwikiproperties").getProperty("scheduler.enabled", true);
+ this.schedulersClassLoaderManager = Utils.getComponent(SchedulersClassLoaderManager.class);
+ this.schedulersClassLoaderManager.setSchedulerPlugin(this);
if (this.enabled) {
Thread thread = new Thread(new ExecutionContextRunnable(new Runnable()
@@ -397,13 +408,9 @@ public boolean scheduleJob(BaseObject object, XWikiContext context) throws Sched
try {
// compute the job unique Id
String xjob = getObjectUniqueId(object);
-
- // Load the job class.
- // Note: Remember to always use the current thread's class loader and not the container's
- // (Class.forName(...)) since otherwise we will not be able to load classes installed with EM.
- ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();
- String jobClassName = object.getStringValue("jobClass");
- Class jobClass = (Class) Class.forName(jobClassName, true, currentThreadClassLoader);
+ String jobClassName = object.getStringValue(SchedulerJobClassDocumentInitializer.FIELD_JOBCLASS);
+ Class jobClass = (Class) this.schedulersClassLoaderManager
+ .loadClassAndRegister(jobClassName, object.getReference());
// Build the new job.
JobBuilder jobBuilder = JobBuilder.newJob(jobClass);
@@ -570,6 +577,7 @@ private void deleteJob(BaseObject object) throws SchedulerPluginException
throw new SchedulerPluginException(SchedulerPluginException.ERROR_SCHEDULERPLUGIN_PAUSE_JOB,
"Error occured while trying to pause job " + object.getStringValue("jobName"), e);
}
+ this.schedulersClassLoaderManager.removeScheduler(object.getReference());
}
/**
@@ -733,13 +741,17 @@ public List getEvents()
@Override
public void onEvent(Event event, Object source, Object data)
{
- if (event instanceof WikiDeletedEvent) {
- String wikiId = ((WikiDeletedEvent) event).getWikiId();
+ if (event instanceof WikiDeletedEvent wikiDeletedEvent) {
+ String wikiId = wikiDeletedEvent.getWikiId();
try {
onWikiDeletedEvent(wikiId);
} catch (SchedulerException e) {
LOGGER.error("Failed to remove schedulers for wiki [{}]", wikiId, e);
}
+ this.schedulersClassLoaderManager.removeSchedulers(new WikiNamespace(wikiId).serialize());
+ } else if (event instanceof ClassLoaderResetEvent classLoaderResetEvent) {
+ String namespace = classLoaderResetEvent.getNamespace();
+ this.schedulersClassLoaderManager.onClassLoaderReset(namespace);
} else {
onDocumentEvent(source, data);
}
diff --git a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/internal/SchedulerJobClassDocumentInitializer.java b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/internal/SchedulerJobClassDocumentInitializer.java
index d884477c2007..5d4b6b7beb98 100644
--- a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/internal/SchedulerJobClassDocumentInitializer.java
+++ b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/internal/SchedulerJobClassDocumentInitializer.java
@@ -56,12 +56,15 @@ public class SchedulerJobClassDocumentInitializer extends AbstractMandatoryClass
public static final LocalDocumentReference XWIKI_JOB_CLASSREFERENCE =
new LocalDocumentReference(XWiki.SYSTEM_SPACE, "SchedulerJobClass");
+ /**
+ * Field containing the class name of the job.
+ */
+ public static final String FIELD_JOBCLASS = "jobClass";
+
private static final String FIELD_JOBNAME = "jobName";
private static final String FIELD_JOBDESCRIPTION = "jobDescription";
- private static final String FIELD_JOBCLASS = "jobClass";
-
private static final String FIELD_STATUS = "status";
private static final String FIELD_CRON = "cron";
diff --git a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/internal/SchedulersClassLoaderManager.java b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/internal/SchedulersClassLoaderManager.java
new file mode 100644
index 000000000000..d9fbad49c69d
--- /dev/null
+++ b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/java/com/xpn/xwiki/plugin/scheduler/internal/SchedulersClassLoaderManager.java
@@ -0,0 +1,157 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package com.xpn.xwiki.plugin.scheduler.internal;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.xwiki.classloader.ClassLoaderManager;
+import org.xwiki.classloader.NamespaceURLClassLoader;
+import org.xwiki.component.annotation.Component;
+import org.xwiki.model.EntityType;
+import org.xwiki.model.reference.EntityReference;
+
+import com.xpn.xwiki.XWikiContext;
+import com.xpn.xwiki.XWikiException;
+import com.xpn.xwiki.doc.XWikiDocument;
+import com.xpn.xwiki.objects.BaseObject;
+import com.xpn.xwiki.objects.BaseObjectReference;
+import com.xpn.xwiki.plugin.scheduler.SchedulerPlugin;
+
+/**
+ * Component dedicated to handle operations related to loading classes for Scheduler.
+ *
+ * @version $Id$
+ * @since 17.10.1
+ * @since 18.0.0RC1
+ */
+@Component(roles = SchedulersClassLoaderManager.class)
+@Singleton
+public class SchedulersClassLoaderManager
+{
+ private SchedulerPlugin schedulerPlugin;
+
+ private final Map> schedulersMapPerNamespace = new HashMap<>();
+
+ @Inject
+ private Provider contextProvider;
+
+ @Inject
+ private Logger logger;
+
+ @Inject
+ private ClassLoaderManager classLoaderManager;
+
+ /**
+ * Define the instance of the scheduler plugin to use.
+ * @param schedulerPlugin the scheduler plugin instance this instance should use.
+ */
+ public void setSchedulerPlugin(SchedulerPlugin schedulerPlugin)
+ {
+ this.schedulerPlugin = schedulerPlugin;
+ }
+
+ private void registerScheduler(String namespace, BaseObjectReference objectReference)
+ {
+ if (!this.schedulersMapPerNamespace.containsKey(namespace)) {
+ this.schedulersMapPerNamespace.put(namespace, new HashSet<>());
+ }
+ this.schedulersMapPerNamespace.get(namespace).add(objectReference);
+ }
+
+ /**
+ * Remove scheduler information related to given object reference.
+ * @param objectReference the reference of a scheduler object.
+ */
+ public void removeScheduler(BaseObjectReference objectReference)
+ {
+ for (Set objectReferenceSet : this.schedulersMapPerNamespace.values()) {
+ objectReferenceSet.remove(objectReference);
+ }
+ }
+
+ /**
+ * Remove all schedulers information associated to a namespace.
+ * @param namespace the namespace for which to remove information.
+ */
+ public void removeSchedulers(String namespace)
+ {
+ this.schedulersMapPerNamespace.remove(namespace);
+ }
+
+ /**
+ * Perform operations when a classloader of a specific namespace is reset.
+ * @param namespace the namespace for which an event has been triggered.
+ */
+ public void onClassLoaderReset(String namespace)
+ {
+ Set objectReferences =
+ new HashSet<>(schedulersMapPerNamespace.getOrDefault(namespace, Set.of()));
+ for (BaseObjectReference objectReference : objectReferences) {
+ this.reloadScheduler(objectReference);
+ }
+ }
+
+ private void reloadScheduler(BaseObjectReference objectReference)
+ {
+ XWikiContext context = contextProvider.get();
+ EntityReference documentReference = objectReference.extractReference(EntityType.DOCUMENT);
+ try {
+ XWikiDocument document = context.getWiki().getDocument(documentReference, context);
+ BaseObject jobObject = document.getXObject(SchedulerJobClassDocumentInitializer.XWIKI_JOB_CLASSREFERENCE);
+ this.schedulerPlugin.unscheduleJob(jobObject, context);
+ this.schedulerPlugin.scheduleJob(jobObject, context);
+ } catch (XWikiException e) {
+ this.logger.error("Error while trying to reload scheduler for object [{}]: ", objectReference, e);
+ }
+ }
+
+ /**
+ * Load a class for a scheduler and register it at the same time.
+ * @param className the name of the class to load.
+ * @param baseObjectReference the reference of the object of the scheduler.
+ * @return the instance of the given class name.
+ * @throws ClassNotFoundException if the class cannot be found.
+ */
+ public Class> loadClassAndRegister(String className, BaseObjectReference baseObjectReference)
+ throws ClassNotFoundException
+ {
+ String namespace = null;
+
+ // Reload the root classloader if needed: it's important if it's been dropped.
+ NamespaceURLClassLoader classLoader = this.classLoaderManager.getURLClassLoader(null, true);
+ Class> result = Class.forName(className, true, classLoader);
+
+ // find the actual namespace of the classloader from where the class has been found.
+ if (result.getClassLoader() instanceof NamespaceURLClassLoader namespaceURLClassLoader) {
+ namespace = namespaceURLClassLoader.getNamespace();
+ }
+
+ this.registerScheduler(namespace, baseObjectReference);
+ return result;
+ }
+}
diff --git a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/resources/META-INF/components.txt
index 84d4d5a99345..749d53b765c0 100644
--- a/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/resources/META-INF/components.txt
+++ b/xwiki-platform-core/xwiki-platform-scheduler/xwiki-platform-scheduler-api/src/main/resources/META-INF/components.txt
@@ -1 +1,2 @@
com.xpn.xwiki.plugin.scheduler.internal.SchedulerJobClassDocumentInitializer
+com.xpn.xwiki.plugin.scheduler.internal.SchedulersClassLoaderManager