diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
index fe256aece..07df374ad 100644
--- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
+++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc
@@ -1229,7 +1229,7 @@ Spring Cloud Sleuth provides instrumentation for https://grpc.io/[gRPC] through
===== Dependencies
IMPORTANT: The gRPC integration relies on two external libraries to instrument clients and servers and both of those libraries must be on the class path to enable the instrumentation.
-Maven:
+Maven:
```
io.github.lognet
@@ -1248,7 +1248,7 @@ Gradle:
===== Server Instrumentation
-Spring Cloud Sleuth leverages grpc-spring-boot-starter to register Brave's gRPC server interceptor with all services annotated with `@GRpcService`.
+Spring Cloud Sleuth leverages grpc-spring-boot-starter to register Brave's gRPC server interceptor with all services annotated with `@GRpcService`.
===== Client Instrumentation
@@ -1388,6 +1388,12 @@ To disable Zuul support, set the `spring.sleuth.zuul.enabled` property to `false
We set `tracing` property to Lettcue `ClientResources` instance to enable Brave tracing built in Lettuce .
To disable Redis support, set the `spring.sleuth.redis.enabled` property to `false`.
+=== Quartz
+
+We instrument quartz jobs by adding Job/Trigger listeners to the Quartz Scheduler.
+
+To turn off this feature, set the `spring.sleuth.quartz.enabled` property to `false`.
+
== Running examples
You can see the running examples deployed in the https://run.pivotal.io/[Pivotal Web Services].
diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml
index c468ba227..c8c91b457 100644
--- a/spring-cloud-sleuth-core/pom.xml
+++ b/spring-cloud-sleuth-core/pom.xml
@@ -289,6 +289,13 @@
true
+
+
+ org.springframework.boot
+ spring-boot-starter-quartz
+ true
+
+
org.springframework.boot
spring-boot-autoconfigure-processor
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfiguration.java
new file mode 100644
index 000000000..460009871
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfiguration.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013-2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.quartz;
+
+import brave.Tracing;
+import org.quartz.Scheduler;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
+import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
+ * Auto-configuration} enables Quartz span information propagation.
+ *
+ * @author Branden Cash
+ * @since 2.2.0
+ */
+@Configuration
+@ConditionalOnBean({ Tracing.class, Scheduler.class })
+@AutoConfigureAfter({ TraceAutoConfiguration.class, QuartzAutoConfiguration.class })
+@ConditionalOnProperty(value = "spring.sleuth.quartz.enabled", matchIfMissing = true)
+public class TraceQuartzAutoConfiguration implements InitializingBean {
+
+ private Scheduler scheduler;
+
+ private Tracing tracing;
+
+ public TraceQuartzAutoConfiguration(Scheduler scheduler, Tracing tracing) {
+ this.scheduler = scheduler;
+ this.tracing = tracing;
+ }
+
+ @Bean
+ public TracingJobListener tracingJobListener() {
+ return new TracingJobListener(tracing);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ scheduler.getListenerManager().addTriggerListener(tracingJobListener());
+ scheduler.getListenerManager().addJobListener(tracingJobListener());
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListener.java
new file mode 100644
index 000000000..dfc028adf
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2013-2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.quartz;
+
+import brave.Span;
+import brave.Tracer.SpanInScope;
+import brave.Tracing;
+import brave.propagation.Propagation.Getter;
+import brave.propagation.TraceContextOrSamplingFlags;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobListener;
+import org.quartz.Trigger;
+import org.quartz.Trigger.CompletedExecutionInstruction;
+import org.quartz.TriggerListener;
+
+/**
+ * {@link org.quartz.JobListener JobListener} that will wrap a span around quartz jobs
+ * when they start and finish.
+ *
+ * @author Branden Cash
+ * @since 2.2.0
+ */
+class TracingJobListener implements JobListener, TriggerListener {
+
+ static final String TRIGGER_TAG_KEY = "quartz.trigger";
+
+ static final String CONTEXT_SPAN_KEY = Span.class.getName();
+
+ static final String CONTEXT_SPAN_IN_SCOPE_KEY = SpanInScope.class.getName();
+
+ private static final Getter GETTER = (carrier, key) -> {
+ Object value = carrier.get(key);
+ if (value instanceof String) {
+ return (String) value;
+ }
+ return null;
+ };
+
+ private final Tracing tracing;
+
+ TracingJobListener(Tracing tracing) {
+ this.tracing = tracing;
+ }
+
+ @Override
+ public String getName() {
+ return getClass().getName();
+ }
+
+ @Override
+ public void triggerFired(Trigger trigger, JobExecutionContext context) {
+ TraceContextOrSamplingFlags extracted = tracing.propagation().extractor(GETTER)
+ .extract(context.getMergedJobDataMap());
+ Span span = tracing.tracer().nextSpan(extracted)
+ .name(context.getTrigger().getJobKey().toString())
+ .tag(TRIGGER_TAG_KEY, context.getTrigger().getKey().toString());
+ context.put(CONTEXT_SPAN_KEY, span);
+ context.put(CONTEXT_SPAN_IN_SCOPE_KEY,
+ tracing.tracer().withSpanInScope(span.start()));
+ }
+
+ @Override
+ public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
+ return false;
+ }
+
+ @Override
+ public void triggerMisfired(Trigger trigger) {
+
+ }
+
+ @Override
+ public void triggerComplete(Trigger trigger, JobExecutionContext context,
+ CompletedExecutionInstruction triggerInstructionCode) {
+ closeTrace(context);
+ }
+
+ @Override
+ public void jobToBeExecuted(JobExecutionContext context) {
+
+ }
+
+ @Override
+ public void jobExecutionVetoed(JobExecutionContext context) {
+ closeTrace(context);
+ }
+
+ @Override
+ public void jobWasExecuted(JobExecutionContext context,
+ JobExecutionException jobException) {
+ }
+
+ private void closeTrace(JobExecutionContext context) {
+ Object spanInScope = context.get(CONTEXT_SPAN_IN_SCOPE_KEY);
+ Object span = context.get(CONTEXT_SPAN_KEY);
+ if (spanInScope instanceof SpanInScope) {
+ ((SpanInScope) spanInScope).close();
+ }
+ if (span instanceof Span) {
+ ((Span) span).finish();
+ }
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories
index 38e80b152..a394fdf86 100644
--- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories
@@ -26,7 +26,8 @@ org.springframework.cloud.sleuth.instrument.messaging.TraceMessagingAutoConfigur
org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration,\
org.springframework.cloud.sleuth.instrument.opentracing.OpentracingAutoConfiguration,\
-org.springframework.cloud.sleuth.instrument.redis.TraceRedisAutoConfiguration
+org.springframework.cloud.sleuth.instrument.redis.TraceRedisAutoConfiguration,\
+org.springframework.cloud.sleuth.instrument.quartz.TraceQuartzAutoConfiguration
# Environment Post Processor
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfigurationTest.java
new file mode 100644
index 000000000..4eedeea91
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfigurationTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2013-2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.quartz;
+
+import brave.Tracer;
+import brave.Tracing;
+import org.junit.Test;
+import org.quartz.ListenerManager;
+import org.quartz.Scheduler;
+
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Branden Cash
+ */
+public class TraceQuartzAutoConfigurationTest {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(SchedulerConfig.class,
+ TracingConfig.class, TraceQuartzAutoConfiguration.class));
+
+ @Test
+ public void should_create_job_listener_bean_when_all_conditions_are_met() {
+ // when
+ this.contextRunner.run(context -> {
+ // expect
+ assertThat(context).hasSingleBean(TracingJobListener.class);
+ });
+ }
+
+ @Test
+ public void should_add_listener_to_trigger_listeners_when_conditions_are_met() {
+ // when
+ this.contextRunner.run(context -> {
+ // expect
+ verify(context.getBean(Scheduler.class).getListenerManager())
+ .addTriggerListener(context.getBean(TracingJobListener.class));
+ });
+ }
+
+ @Test
+ public void should_add_listener_job_listeners_when_conditions_are_met() {
+ // when
+ this.contextRunner.run(context -> {
+ // expect
+ verify(context.getBean(Scheduler.class).getListenerManager())
+ .addJobListener(context.getBean(TracingJobListener.class));
+ });
+ }
+
+ @Test
+ public void should_not_create_listener_bean_when_tracing_bean_is_not_present() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations
+ // given
+ .of(SchedulerConfig.class, TraceQuartzAutoConfiguration.class))
+
+ // when
+ .run(context -> {
+ // expect
+ assertThat(context).doesNotHaveBean(TracingJobListener.class);
+ });
+ }
+
+ @Test
+ public void should_not_create_listener_when_scheduler_bean_is_not_present() {
+ new ApplicationContextRunner().withConfiguration(AutoConfigurations
+ // given
+ .of(TracingConfig.class, TraceQuartzAutoConfiguration.class))
+
+ // when
+ .run(context -> {
+ // expect
+ assertThat(context).doesNotHaveBean(TracingJobListener.class);
+ });
+ }
+
+ @Test
+ public void should_not_create_listener_when_sleuth_is_disabled() {
+ // when
+ this.contextRunner.withPropertyValues("spring.sleuth.quartz.enabled=false")
+ // expect
+ .run(context -> {
+ assertThat(context).doesNotHaveBean(TracingJobListener.class);
+ });
+ }
+
+ @Configuration
+ @AutoConfigureBefore(TraceQuartzAutoConfiguration.class)
+ public static class SchedulerConfig {
+
+ @Bean
+ public Scheduler scheduler() throws Exception {
+ Scheduler scheduler = mock(Scheduler.class);
+ when(scheduler.getListenerManager()).thenReturn(mock(ListenerManager.class));
+ return scheduler;
+ }
+
+ }
+
+ @Configuration
+ @AutoConfigureBefore(TraceQuartzAutoConfiguration.class)
+ public static class TracingConfig {
+
+ @Bean
+ public Tracing tracing() {
+ return mock(Tracing.class);
+ }
+
+ @Bean
+ public Tracer tracer() {
+ return mock(Tracer.class);
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListenerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListenerTest.java
new file mode 100644
index 000000000..53ea9f711
--- /dev/null
+++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListenerTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2013-2019 the original author or authors.
+ *
+ * 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.quartz;
+
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+
+import brave.Tracer.SpanInScope;
+import brave.Tracing;
+import brave.propagation.Propagation.Setter;
+import brave.propagation.StrictScopeDecorator;
+import brave.propagation.ThreadLocalCurrentTraceContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.quartz.JobKey;
+import org.quartz.JobListener;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.Trigger.CompletedExecutionInstruction;
+import org.quartz.TriggerKey;
+import org.quartz.TriggerListener;
+import org.quartz.impl.StdSchedulerFactory;
+import org.quartz.listeners.JobListenerSupport;
+import org.quartz.listeners.TriggerListenerSupport;
+import org.quartz.utils.StringKeyDirtyFlagMap;
+import zipkin2.Span;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.quartz.JobBuilder.newJob;
+import static org.quartz.TriggerBuilder.newTrigger;
+import static org.springframework.cloud.sleuth.instrument.quartz.TracingJobListener.CONTEXT_SPAN_IN_SCOPE_KEY;
+import static org.springframework.cloud.sleuth.instrument.quartz.TracingJobListener.CONTEXT_SPAN_KEY;
+import static org.springframework.cloud.sleuth.instrument.quartz.TracingJobListener.TRIGGER_TAG_KEY;
+
+/**
+ * @author Branden Cash
+ */
+public class TracingJobListenerTest {
+
+ private static final JobKey SUCCESSFUL_JOB_KEY = new JobKey("SuccessfulJob");
+
+ private static final JobKey EXCEPTIONAL_JOB_KEY = new JobKey("ExceptionalJob");
+
+ private static final TriggerKey TRIGGER_KEY = new TriggerKey("ExampleTrigger");
+
+ private TracingJobListener listener;
+
+ private Tracing tracing;
+
+ private Scheduler scheduler;
+
+ private CompletableFuture completableJob;
+
+ private Queue spans = new ArrayDeque<>();
+
+ @Before
+ public void setUp() throws Exception {
+ tracing = Tracing.newBuilder().spanReporter(spans::add)
+ .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
+ .addScopeDecorator(StrictScopeDecorator.create()).build())
+ .build();
+
+ listener = new TracingJobListener(tracing);
+ completableJob = new CompleteableTriggerListener();
+
+ scheduler = createScheduler(getClass().getSimpleName(), 1);
+ scheduler.addJob(newJob(ExceptionalJob.class).withIdentity(EXCEPTIONAL_JOB_KEY)
+ .storeDurably().build(), true);
+ scheduler.addJob(newJob(SuccessfulJob.class).withIdentity(SUCCESSFUL_JOB_KEY)
+ .storeDurably().build(), true);
+
+ scheduler.getListenerManager().addTriggerListener(listener);
+ scheduler.getListenerManager().addJobListener(listener);
+ scheduler.getListenerManager()
+ .addTriggerListener((CompleteableTriggerListener) completableJob);
+ scheduler.getListenerManager()
+ .addJobListener((CompleteableTriggerListener) completableJob);
+ scheduler.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ scheduler.shutdown(true);
+ }
+
+ @Test
+ public void should_return_class_name_all_the_time() {
+ // when
+ String name = listener.getName();
+
+ // expect
+ assertThat(name).isEqualTo(TracingJobListener.class.getName());
+ }
+
+ @Test
+ public void should_complete_span_when_job_is_successful() throws Exception {
+ // given
+ Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).startNow().build();
+
+ // when
+ runJob(trigger);
+
+ // expect
+ takeSpan();
+ }
+
+ @Test
+ public void should_have_span_with_proper_name_and_tag_when_job_is_successful()
+ throws Exception {
+ // given
+ Trigger trigger = newTrigger().withIdentity(TRIGGER_KEY)
+ .forJob(SUCCESSFUL_JOB_KEY).startNow().build();
+
+ // when
+ runJob(trigger);
+
+ // expect
+ Span span = takeSpan();
+ assertThat(span.name()).isEqualToIgnoringCase(SUCCESSFUL_JOB_KEY.toString());
+ assertThat(span.tags().get(TRIGGER_TAG_KEY))
+ .isEqualToIgnoringCase(TRIGGER_KEY.toString());
+ }
+
+ @Test
+ public void should_complete_span_when_job_throws_exception() throws Exception {
+ // given
+ Trigger trigger = newTrigger().forJob(EXCEPTIONAL_JOB_KEY).startNow().build();
+
+ // when
+ runJob(trigger);
+
+ // expect
+ takeSpan();
+ }
+
+ @Test
+ public void should_complete_span_when_job_is_vetoed() throws Exception {
+ // given
+ scheduler.getListenerManager().addTriggerListener(new VetoJobTriggerListener());
+ Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).startNow().build();
+
+ // when
+ runJob(trigger);
+
+ // expect
+ takeSpan();
+ }
+
+ @Test
+ public void should_not_complete_span_when_context_is_modified_to_remove_keys()
+ throws Exception {
+ // given
+ scheduler.getListenerManager().addJobListener(new ContextModifyingJobListener());
+ Trigger trigger = newTrigger().forJob(EXCEPTIONAL_JOB_KEY).startNow().build();
+
+ // when
+ runJob(trigger);
+
+ // expect
+ requireNoSpan();
+ }
+
+ @Test
+ public void should_have_parent_and_child_span_when_trigger_contains_span_info()
+ throws Exception {
+ // given
+ brave.Span span = tracing.tracer().nextSpan();
+ JobDataMap data = new JobDataMap();
+ addSpanToJobData(data);
+ Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).usingJobData(data)
+ .startNow().build();
+
+ // when
+ runJob(trigger);
+
+ // expect
+ Span parent = takeSpan();
+ Span child = takeSpan();
+ assertThat(parent.parentId()).isNull();
+ assertThat(child.parentId()).isEqualTo(parent.id());
+ }
+
+ @Test
+ public void should_have_parent_and_child_span_when_trigger_job_data_was_created_with_differently_typed_map()
+ throws Exception {
+ // given
+ JobDataMap data = new JobDataMap(new HashMap());
+ addSpanToJobData(data);
+ Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).usingJobData(data)
+ .startNow().build();
+
+ // when
+ runJob(trigger);
+
+ // expect
+ Span parent = takeSpan();
+ Span child = takeSpan();
+ assertThat(parent.parentId()).isNull();
+ assertThat(child.parentId()).isEqualTo(parent.id());
+ }
+
+ void runJob(Trigger trigger) throws SchedulerException {
+ scheduler.scheduleJob(trigger);
+ completableJob.join();
+ }
+
+ Scheduler createScheduler(String name, int threadPoolSize) throws SchedulerException {
+ Properties config = new Properties();
+ config.setProperty("org.quartz.scheduler.instanceName", name + "Scheduler");
+ config.setProperty("org.quartz.scheduler.instanceId", "AUTO");
+ config.setProperty("org.quartz.threadPool.threadCount",
+ Integer.toString(threadPoolSize));
+ config.setProperty("org.quartz.threadPool.class",
+ "org.quartz.simpl.SimpleThreadPool");
+ return new StdSchedulerFactory(config).getScheduler();
+ }
+
+ void addSpanToJobData(JobDataMap data) {
+ brave.Span span = tracing.tracer().nextSpan();
+ try (SpanInScope spanInScope = tracing.tracer().withSpanInScope(span)) {
+ tracing.propagation()
+ .injector((Setter) StringKeyDirtyFlagMap::put)
+ .inject(tracing.currentTraceContext().get(), data);
+ }
+ finally {
+ span.finish();
+ }
+ }
+
+ Span takeSpan() throws InterruptedException {
+ Span result = spans.poll();
+ assertThat(result).withFailMessage("Span was not reported, but was expected")
+ .isNotNull();
+ return result;
+ }
+
+ void requireNoSpan() throws InterruptedException {
+ Span result = spans.poll();
+ assertThat(result).withFailMessage("Span was reported, but was not expected")
+ .isNull();
+ }
+
+ public static class CompleteableTriggerListener extends CompletableFuture
+ implements TriggerListener, JobListener {
+
+ @Override
+ public String getName() {
+ return getClass().getName();
+ }
+
+ @Override
+ public void triggerFired(Trigger trigger, JobExecutionContext context) {
+ }
+
+ @Override
+ public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
+ return false;
+ }
+
+ @Override
+ public void triggerMisfired(Trigger trigger) {
+ }
+
+ @Override
+ public void triggerComplete(Trigger trigger, JobExecutionContext context,
+ CompletedExecutionInstruction triggerInstructionCode) {
+ complete(context.getResult());
+ }
+
+ @Override
+ public void jobToBeExecuted(JobExecutionContext context) {
+ }
+
+ @Override
+ public void jobExecutionVetoed(JobExecutionContext context) {
+ complete(context.getResult());
+ }
+
+ @Override
+ public void jobWasExecuted(JobExecutionContext context,
+ JobExecutionException jobException) {
+ }
+
+ }
+
+ public static class VetoJobTriggerListener extends TriggerListenerSupport {
+
+ @Override
+ public String getName() {
+ return getClass().getName();
+ }
+
+ @Override
+ public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
+ return true;
+ }
+
+ }
+
+ public static class ContextModifyingJobListener extends JobListenerSupport {
+
+ @Override
+ public String getName() {
+ return getClass().getName();
+ }
+
+ @Override
+ public void jobToBeExecuted(JobExecutionContext context) {
+ context.put(CONTEXT_SPAN_KEY, null);
+ context.put(CONTEXT_SPAN_IN_SCOPE_KEY, null);
+ }
+
+ }
+
+ public static class ExceptionalJob implements Job {
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+ throw new RuntimeException("Intentional Exception");
+ }
+
+ }
+
+ public static class SuccessfulJob implements Job {
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+ }
+
+ }
+
+}