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