All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.component.quartz.QuartzComponent Maven / Gradle / Ivy

The newest version!
/*
 * 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.camel.component.quartz;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.ExtendedStartupListener;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.DefaultComponent;
import org.apache.camel.support.ResourceHelper;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.PropertiesHelper;
import org.apache.camel.util.StringHelper;
import org.quartz.Scheduler;
import org.quartz.SchedulerContext;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This component will hold a Quartz Scheduler that will provide a scheduled timer based endpoint that generate a
 * QuartzMessage to a route.
 */
@Component("quartz")
public class QuartzComponent extends DefaultComponent implements ExtendedStartupListener {

    private static final Logger LOG = LoggerFactory.getLogger(QuartzComponent.class);

    private final List schedulerInitTasks = new ArrayList<>();
    private volatile boolean schedulerInitTasksDone;
    private volatile boolean shouldStopScheduler;

    @Metadata(label = "advanced", autowired = true)
    private Scheduler scheduler;
    @Metadata(label = "advanced")
    private SchedulerFactory schedulerFactory;
    @Metadata
    private String propertiesRef;
    @Metadata
    private Map properties;
    @Metadata(supportFileReference = true)
    private String propertiesFile;
    @Metadata(label = "scheduler", defaultValue = "true")
    private boolean autoStartScheduler = true;
    @Metadata(label = "scheduler")
    private boolean interruptJobsOnShutdown;
    @Metadata(defaultValue = "true")
    private boolean enableJmx = true;
    @Metadata
    private boolean prefixJobNameWithEndpointId;
    @Metadata(defaultValue = "true")
    private boolean prefixInstanceName = true;
    @UriParam(label = "advanced")
    private boolean ignoreExpiredNextFireTime;

    public QuartzComponent() {
    }

    public QuartzComponent(CamelContext camelContext) {
        super(camelContext);
    }

    public boolean isAutoStartScheduler() {
        return autoStartScheduler;
    }

    /**
     * Whether the scheduler should be auto started.
     * 

* This option is default true */ public void setAutoStartScheduler(boolean autoStartScheduler) { this.autoStartScheduler = autoStartScheduler; } public boolean isPrefixJobNameWithEndpointId() { return prefixJobNameWithEndpointId; } /** * Whether to prefix the quartz job with the endpoint id. *

* This option is default false. */ public void setPrefixJobNameWithEndpointId(boolean prefixJobNameWithEndpointId) { this.prefixJobNameWithEndpointId = prefixJobNameWithEndpointId; } public boolean isEnableJmx() { return enableJmx; } /** * Whether to enable Quartz JMX, which allows managing the Quartz scheduler from JMX. *

* The default value for this option is true. */ public void setEnableJmx(boolean enableJmx) { this.enableJmx = enableJmx; } public String getPropertiesRef() { return propertiesRef; } /** * References to an existing {@link Properties} or {@link Map} to lookup in the registry to use for configuring * quartz. */ public void setPropertiesRef(String propertiesRef) { this.propertiesRef = propertiesRef; } public Map getProperties() { return properties; } /** * Properties to configure the Quartz scheduler. */ public void setProperties(Map properties) { this.properties = properties; } public String getPropertiesFile() { return propertiesFile; } /** * File name of the properties to load from the classpath */ public void setPropertiesFile(String propertiesFile) { this.propertiesFile = propertiesFile; } public boolean isPrefixInstanceName() { return prefixInstanceName; } /** * Whether to prefix the Quartz Scheduler instance name with the CamelContext name. *

* This is enabled by default, to let each CamelContext use its own Quartz scheduler instance by default. You can * set this option to false to reuse Quartz scheduler instances between multiple CamelContext's. */ public void setPrefixInstanceName(boolean prefixInstanceName) { this.prefixInstanceName = prefixInstanceName; } public boolean isInterruptJobsOnShutdown() { return interruptJobsOnShutdown; } /** * Whether to interrupt jobs on shutdown, which forces the scheduler to shut down quicker and attempt to interrupt * any running jobs. If this is enabled, then any running jobs can fail due to being interrupted. When a job is * interrupted then Camel will mark the exchange to stop to continue routing and set * {@link java.util.concurrent.RejectedExecutionException} as caused exception. Therefore, use this with care, as * its often better to allow Camel jobs to complete and shutdown gracefully. */ public void setInterruptJobsOnShutdown(boolean interruptJobsOnShutdown) { this.interruptJobsOnShutdown = interruptJobsOnShutdown; } public boolean isIgnoreExpiredNextFireTime() { return ignoreExpiredNextFireTime; } /** * Whether to ignore quartz cannot schedule a trigger because the trigger will never fire in the future. This can * happen when using a cron trigger configured to only run in the past. * * By default, Quartz will fail to schedule the trigger and therefore fail to start the Camel route. You can set * this to true which then logs a WARN and then ignore the problem, meaning that the route will never fire in the * future. */ public void setIgnoreExpiredNextFireTime(boolean ignoreExpiredNextFireTime) { this.ignoreExpiredNextFireTime = ignoreExpiredNextFireTime; } public SchedulerFactory getSchedulerFactory() { if (schedulerFactory == null) { try { schedulerFactory = createSchedulerFactory(); } catch (SchedulerException e) { throw new RuntimeException(e); } } return schedulerFactory; } private SchedulerFactory createSchedulerFactory() throws SchedulerException { SchedulerFactory answer; Properties prop = loadProperties(); if (prop != null) { // force disabling update checker (will do online check over the internet) prop.put("org.quartz.scheduler.skipUpdateCheck", "true"); prop.put("org.terracotta.quartz.skipUpdateCheck", "true"); // camel context name will be a suffix to use one scheduler per context if (isPrefixInstanceName()) { String instName = createInstanceName(prop); prop.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, instName); } if (isInterruptJobsOnShutdown()) { prop.setProperty(StdSchedulerFactory.PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN, "true"); } // enable jmx unless configured to not do so if (enableJmx && !prop.containsKey("org.quartz.scheduler.jmx.export")) { prop.put("org.quartz.scheduler.jmx.export", "true"); LOG.info("Setting org.quartz.scheduler.jmx.export=true to ensure QuartzScheduler(s) will be enlisted in JMX"); } answer = new StdSchedulerFactory(prop); } else { // read default props to be able to use a single scheduler per camel context // if we need more than one scheduler per context use setScheduler(Scheduler) // or setFactory(SchedulerFactory) methods // must use classloader from StdSchedulerFactory to work even in OSGi InputStream is = StdSchedulerFactory.class.getClassLoader().getResourceAsStream("org/quartz/quartz.properties"); if (is == null) { throw new SchedulerException("Quartz properties file not found in classpath: org/quartz/quartz.properties"); } prop = new Properties(); try { prop.load(is); } catch (IOException e) { throw new SchedulerException( "Error loading Quartz properties file from classpath: org/quartz/quartz.properties", e); } finally { IOHelper.close(is); } // camel context name will be a suffix to use one scheduler per context if (isPrefixInstanceName()) { // camel context name will be a suffix to use one scheduler per context String instName = createInstanceName(prop); prop.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, instName); } // force disabling update checker (will do online check over the internet) prop.put("org.quartz.scheduler.skipUpdateCheck", "true"); prop.put("org.terracotta.quartz.skipUpdateCheck", "true"); if (isInterruptJobsOnShutdown()) { prop.setProperty(StdSchedulerFactory.PROP_SCHED_INTERRUPT_JOBS_ON_SHUTDOWN, "true"); } // enable jmx unless configured to not do so if (enableJmx && !prop.containsKey("org.quartz.scheduler.jmx.export")) { prop.put("org.quartz.scheduler.jmx.export", "true"); LOG.info("Setting org.quartz.scheduler.jmx.export=true to ensure QuartzScheduler(s) will be enlisted in JMX"); } answer = new StdSchedulerFactory(prop); } if (LOG.isDebugEnabled()) { String name = prop.getProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME); LOG.debug("Creating SchedulerFactory: {} with properties: {}", name, prop); } return answer; } protected String createInstanceName(Properties prop) { String instName = prop.getProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME); // camel context name will be a suffix to use one scheduler per context String identity = QuartzHelper.getQuartzContextName(getCamelContext()); if (identity != null) { if (instName == null) { instName = "scheduler-" + identity; } else { instName = instName + "-" + identity; } } return instName; } /** * Is the quartz scheduler clustered? */ public boolean isClustered() throws SchedulerException { return getScheduler().getMetaData().isJobStoreClustered(); } private Properties loadProperties() throws SchedulerException { Properties answer = null; if (getProperties() != null) { answer = new Properties(); answer.putAll(getProperties()); } if (answer == null && getPropertiesRef() != null) { Map map = CamelContextHelper.mandatoryLookup(getCamelContext(), getPropertiesRef(), Map.class); answer = new Properties(); answer.putAll(map); } if (answer == null && getPropertiesFile() != null) { LOG.info("Loading Quartz properties file from: {}", getPropertiesFile()); InputStream is = null; try { is = ResourceHelper.resolveMandatoryResourceAsInputStream(getCamelContext(), getPropertiesFile()); answer = new Properties(); answer.load(is); } catch (IOException e) { throw new SchedulerException("Error loading Quartz properties file: " + getPropertiesFile(), e); } finally { IOHelper.close(is); } } return answer; } /** * To use the custom SchedulerFactory which is used to create the Scheduler. */ public void setSchedulerFactory(SchedulerFactory schedulerFactory) { this.schedulerFactory = schedulerFactory; } public Scheduler getScheduler() { return scheduler; } /** * Adds a task to be executed as part of initializing and starting the scheduler; or executes the task if the * scheduler has already been started. */ public void addScheduleInitTask(SchedulerInitTask task) { if (schedulerInitTasksDone) { // task already done then run the task now try { task.initializeTask(scheduler); } catch (Exception e) { throw RuntimeCamelException.wrapRuntimeException(e); } } else { this.schedulerInitTasks.add(task); } } /** * To use the custom configured Quartz scheduler, instead of creating a new Scheduler. */ public void setScheduler(Scheduler scheduler) { this.scheduler = scheduler; } @Override protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception { Boolean autoStartScheduler = getAndRemoveParameter(parameters, "autoStartScheduler", Boolean.class); if (autoStartScheduler != null) { this.autoStartScheduler = autoStartScheduler; } Boolean prefixJobNameWithEndpointId = getAndRemoveParameter(parameters, "prefixJobNameWithEndpointId", Boolean.class); if (prefixJobNameWithEndpointId != null) { this.prefixJobNameWithEndpointId = prefixJobNameWithEndpointId; } // Extract trigger.XXX and job.XXX properties to be set on the endpoint below Map triggerParameters = PropertiesHelper.extractProperties(parameters, "trigger."); Map jobParameters = PropertiesHelper.extractProperties(parameters, "job."); // Create quartz endpoint QuartzEndpoint result = new QuartzEndpoint(uri, this); TriggerKey triggerKey = createTriggerKey(uri, remaining, result); result.setTriggerKey(triggerKey); result.setTriggerParameters(triggerParameters); result.setJobParameters(jobParameters); if (autoStartScheduler != null) { result.setAutoStartScheduler(autoStartScheduler); } if (prefixJobNameWithEndpointId != null) { result.setPrefixJobNameWithEndpointId(prefixJobNameWithEndpointId); } // special for cron where we replace + as space String cron = getAndRemoveParameter(parameters, "cron", String.class); if (cron != null) { // replace + as space cron = cron.replace('+', ' '); result.setCron(cron); } result.setIgnoreExpiredNextFireTime(ignoreExpiredNextFireTime); setProperties(result, parameters); return result; } private TriggerKey createTriggerKey(String uri, String remaining, QuartzEndpoint endpoint) throws Exception { // Parse uri for trigger name and group URI u = new URI(uri); String path = StringHelper.after(u.getPath(), "/"); String host = u.getHost(); // host can be null if the uri did contain invalid host characters such as an underscore if (host == null) { host = StringHelper.before(remaining, "/"); if (host == null) { host = remaining; } } // Trigger group can be optional, if so set it to this context's unique name String name; String group; if (ObjectHelper.isNotEmpty(path) && ObjectHelper.isNotEmpty(host)) { group = host; name = path; } else { String camelContextName = QuartzHelper.getQuartzContextName(getCamelContext()); group = camelContextName == null ? "Camel" : "Camel_" + camelContextName; name = host; } if (prefixJobNameWithEndpointId) { name = endpoint.getId() + "_" + name; } return new TriggerKey(name, group); } @Override protected void doStart() throws Exception { super.doStart(); if (scheduler == null) { createAndInitScheduler(); } } private void createAndInitScheduler() throws SchedulerException { LOG.debug("Creating and initializing Quartz scheduler"); shouldStopScheduler = true; scheduler = createScheduler(); SchedulerContext quartzContext = storeCamelContextInQuartzContext(); // Set camel job counts to zero. We needed this to prevent shutdown in case there are multiple Camel contexts // that have not completed yet, and the last one with job counts to zero will eventually shut down. quartzContext.computeIfAbsent(QuartzConstants.QUARTZ_CAMEL_JOBS_COUNT, k -> new AtomicInteger()); } private SchedulerContext storeCamelContextInQuartzContext() throws SchedulerException { // Store CamelContext into QuartzContext space SchedulerContext quartzContext = scheduler.getContext(); String camelContextName = QuartzHelper.getQuartzContextName(getCamelContext()); LOG.debug("Storing camelContextName={} into Quartz Context space", camelContextName); quartzContext.put(QuartzConstants.QUARTZ_CAMEL_CONTEXT + "-" + camelContextName, getCamelContext()); return quartzContext; } private Scheduler createScheduler() throws SchedulerException { return getSchedulerFactory().getScheduler(); } @Override protected void doStop() throws Exception { super.doStop(); if (scheduler != null && shouldStopScheduler) { if (isInterruptJobsOnShutdown()) { LOG.info("Shutting down Quartz scheduler (will interrupts jobs to shutdown quicker)"); scheduler.shutdown(false); scheduler = null; } else { AtomicInteger number = (AtomicInteger) scheduler.getContext().get(QuartzConstants.QUARTZ_CAMEL_JOBS_COUNT); if (number != null && number.get() > 0) { LOG.info("Cannot shutdown Quartz scheduler: {} as there are still {} jobs registered", scheduler.getSchedulerName(), number.get()); } else { LOG.info("Shutting down Quartz scheduler (will wait for all jobs to complete first)"); scheduler.shutdown(true); scheduler = null; } } } } @Override public void onCamelContextStarted(CamelContext context, boolean alreadyStarted) throws Exception { if (alreadyStarted) { // a route may have been added or starter after CamelContext is started, so ensure we start the scheduler doStartScheduler(); } } @Override public void onCamelContextFullyStarted(CamelContext context, boolean alreadyStarted) throws Exception { doStartScheduler(); } protected void doStartScheduler() throws Exception { // If Camel has already started and then user adds a route dynamically, we need to ensure // to create and init the scheduler first. if (scheduler == null) { createAndInitScheduler(); } else { // in case custom scheduler was injected (i.e., created elsewhere); we may need to add // current camel context to quartz context so jobs have access storeCamelContextInQuartzContext(); } // initialize scheduler tasks for (SchedulerInitTask task : schedulerInitTasks) { task.initializeTask(scheduler); } // cleanup tasks as they need only to be triggered once schedulerInitTasks.clear(); schedulerInitTasksDone = true; // Now the scheduler is ready, let's see how we should start it. if (!autoStartScheduler) { LOG.info("Not starting Quartz scheduler: {} because autoStartScheduler is set to false", scheduler); } else { if (scheduler.isStarted()) { LOG.info("The Quartz scheduler: {} has already been started", scheduler); } else { LOG.info("Starting Quartz scheduler: {}", scheduler); scheduler.start(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy