You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2017/11/29 14:51:48 UTC
[camel] 01/08: Added support for AWS XRay tracing of Camel's
exchange routing capabilities which works similar to the OpenTracing tracer
support camel-opentracing provides. This implementation differs, however,
slightly from the one of OpenTracing due to the thread-local nature of an
AWS XRay segment and its behavior if a new segment is created if already an
existing segment is present in the context of the current thread. Upon
exchange creation it is first checked whether an existing segment is
already [...]
This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 3dc9ae1929d9078e6c9cf7beb215ddbee4c478c0
Author: Roman Vottner <ro...@gmx.at>
AuthorDate: Tue Oct 31 11:44:13 2017 +0100
Added support for AWS XRay tracing of Camel's exchange routing capabilities which works similar to the OpenTracing tracer support camel-opentracing provides. This implementation differs, however, slightly from the one of OpenTracing due to the thread-local nature of an AWS XRay segment and its behavior if a new segment is created if already an existing segment is present in the context of the current thread. Upon exchange creation it is first checked whether an existing segment is alr [...]
By default no tracing strategy (= InterceptStragegy) is applied though a tracing subsegment can be created on utilizing EIPTracingStrategy. If only @XRayTrace annotated beans and processors (which keep the actual business logic in it in our case) should be tracked TraceAnnotatedTracingStrategy can be used instead.
---
components/camel-aws-xray/pom.xml | 101 ++++++
.../camel-aws-xray/src/main/docs/awsxray.adoc | 93 ++++++
.../component/aws/xray/EIPTracingStrategy.java | 58 ++++
.../component/aws/xray/NoopTracingStrategy.java | 32 ++
.../aws/xray/TraceAnnotatedTracingStrategy.java | 93 ++++++
.../apache/camel/component/aws/xray/XRayTrace.java | 37 +++
.../camel/component/aws/xray/XRayTracer.java | 351 +++++++++++++++++++++
.../camel/component/aws/xray/ABCRouteTest.java | 80 +++++
.../camel/component/aws/xray/BeanTracingTest.java | 107 +++++++
.../aws/xray/CamelAwsXRayTestSupport.java | 93 ++++++
.../aws/xray/ClientRecipientListRouteTest.java | 66 ++++
.../camel/component/aws/xray/EIPTracingTest.java | 116 +++++++
.../component/aws/xray/ErrorHandlingTest.java | 156 +++++++++
.../apache/camel/component/aws/xray/ErrorTest.java | 138 ++++++++
.../camel/component/aws/xray/FakeAWSDaemon.java | 251 +++++++++++++++
.../aws/xray/MulticastParallelRouteTest.java | 73 +++++
.../component/aws/xray/MulticastRouteTest.java | 74 +++++
.../component/aws/xray/Route2ConcurrentTest.java | 75 +++++
.../component/aws/xray/RouteConcurrentTest.java | 61 ++++
.../aws/xray/SpringAwsXRaySimpleRouteTest.java | 77 +++++
.../camel/component/aws/xray/TestDataBuilder.java | 210 ++++++++++++
.../apache/camel/component/aws/xray/TestUtils.java | 161 ++++++++++
.../camel/component/aws/xray/TwoService2Test.java | 60 ++++
.../camel/component/aws/xray/TwoServiceTest.java | 60 ++++
.../aws/xray/TwoServiceWithExcludeTest.java | 65 ++++
.../src/test/resources/logback-test.xml | 47 +++
.../AwsXRaySimpleRouteTest.xml | 45 +++
components/pom.xml | 1 +
28 files changed, 2781 insertions(+)
diff --git a/components/camel-aws-xray/pom.xml b/components/camel-aws-xray/pom.xml
new file mode 100644
index 0000000..9fe4377
--- /dev/null
+++ b/components/camel-aws-xray/pom.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<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>
+ <artifactId>components</artifactId>
+ <groupId>org.apache.camel</groupId>
+ <version>2.19.2-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-aws-xray</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Camel :: AWS :: XRay</name>
+ <description>Distributed tracing using AWS XRay</description>
+
+ <properties>
+ <aws-xray.version>1.2.0</aws-xray.version>
+ <firstVersion>2.19.2</firstVersion>
+ <label>monitoring,microservice</label>
+ <title>XRay</title>
+
+ <camel.osgi.export.pkg>org.apache.camel.aws-xray.*</camel.osgi.export.pkg>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>com.amazonaws</groupId>
+ <artifactId>aws-xray-recorder-sdk-bom</artifactId>
+ <version>${aws-xray.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-core</artifactId>
+ </dependency>
+
+ <!-- AWS XRay -->
+ <dependency>
+ <!-- Basic functionality for creating segments and transmitting segments. Includes
+ AWSXRayServletFilter for instrumenting incoming requests -->
+ <groupId>com.amazonaws</groupId>
+ <artifactId>aws-xray-recorder-sdk-core</artifactId>
+ </dependency>
+ <dependency>
+ <!-- Instruments calls to AWS services made with AWS SDK for Java clients by adding a tracing
+ client as a request handler -->
+ <groupId>com.amazonaws</groupId>
+ <artifactId>aws-xray-recorder-sdk-aws-sdk</artifactId>
+ </dependency>
+ <dependency>
+ <!-- Instruments outbound HTTP calls made with Apache HTTP clients -->
+ <groupId>com.amazonaws</groupId>
+ <artifactId>aws-xray-recorder-sdk-apache-http</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <version>1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test-spring</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>20170516</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+</project>
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/main/docs/awsxray.adoc b/components/camel-aws-xray/src/main/docs/awsxray.adoc
new file mode 100644
index 0000000..ffae7aa
--- /dev/null
+++ b/components/camel-aws-xray/src/main/docs/awsxray.adoc
@@ -0,0 +1,93 @@
+[[AWSXRay-AWSXRayComponent]]
+## AWS XRay Component
+
+*Available as of Camel 2.20*
+
+The camel-aws-xray component is used for tracing and timing incoming and outgoing Camel messages using https://aws.amazon.com/de/xray/[AWS XRay].
+
+Events (subsegments) are captured for incoming and outgoing messages being sent to/from Camel.
+
+### Dependency
+
+In order to include AWS XRay support into Camel, the archive containing the Camel related AWS XRay related classes need to be added to the project. In addition to that, AWS XRay libraries also need to be available.
+
+To include both, AWS XRay and Camel, dependencies use the following Maven imports:
+
+[source,xml]
+---------------------------------------------------------------------------------------------------------
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>com.amazonaws</groupId>
+ <artifactId>aws-xray-recorder-sdk-bom</artifactId>
+ <version>1.2.0</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-aws.xray</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.amazonaws</groupId>
+ <artifactId>aws-xray-recorder-sdk-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.amazonaws</groupId>
+ <artifactId>aws-xray-recorder-sdk-aws-sdk</artifactId>
+ </dependency>
+ <dependencies>
+---------------------------------------------------------------------------------------------------------
+
+### Configuration
+
+The configuration properties for the AWS XRay tracer are:
+
+[width="100%",cols="10%,10%,80%",options="header",]
+|=======================================================================
+|Option |Default |Description
+
+|excludePatterns | | Sets exclude pattern(s) that will disable tracing for Camel
+messages that matches the pattern. The content is a Set<String> where the key is a pattern matching routeId's. The pattern
+uses the rules from link:intercept.html[Intercept].
+
+|=======================================================================
+
+There is currently only one way an AWS XRay tracer can be configured to provide distributed tracing for a Camel application:
+
+#### Explicit
+
+Include the `camel-aws-xray` component in your POM, along with any specific dependencies associated with the AWS XRay Tracer.
+
+To explicitly configure AWS XRay support, instantiate the `XRayTracer` and initialize the camel
+context. You can optionally specify a `Tracer`, or alternatively it can be implicitly discovered using the
+`Registry` or `ServiceLoader`.
+
+[source,java]
+--------------------------------------------------------------------------------------------------
+XRayTracer xrayTracer = new XRayTracer();
+// By default it uses a NoopTracingStrategy, but you can override it with a specific InterceptStrategy implementation.
+xrayTracer.setTracingStrategy(...);
+// And then initialize the context
+xrayTracer.init(camelContext);
+--------------------------------------------------------------------------------------------------
+
+To use XRayTracer in XML, all you need to do is to define the
+AWS XRay tracer bean. Camel will automatically discover and use it.
+
+[source,xml]
+---------------------------------------------------------------------------------------------------------
+ <bean id="tracingStrategy" class="..."/>
+ <bean id="aws-xray-tracer" class="org.apache.camel.component.aws.xray.XRayTracer" />
+ <property name="tracer" ref="tracingStrategy"/>
+ </bean>
+---------------------------------------------------------------------------------------------------------
+
+### Example
+
+You can find an example demonstrating the way to configure AWS XRay tracing within the tests accompanying this project.
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/EIPTracingStrategy.java b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/EIPTracingStrategy.java
new file mode 100644
index 0000000..c91cdf8
--- /dev/null
+++ b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/EIPTracingStrategy.java
@@ -0,0 +1,58 @@
+/**
+ * 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.aws.xray;
+
+import com.amazonaws.xray.AWSXRay;
+import com.amazonaws.xray.entities.Subsegment;
+import java.lang.invoke.MethodHandles;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.model.ProcessorDefinition;
+import org.apache.camel.processor.DelegateAsyncProcessor;
+import org.apache.camel.spi.InterceptStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EIPTracingStrategy implements InterceptStrategy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Override
+ public Processor wrapProcessorInInterceptors(CamelContext camelContext,
+ ProcessorDefinition<?> processorDefinition, Processor target, Processor nextTarget)
+ throws Exception {
+
+ String defName = processorDefinition.getShortName();
+
+ return new DelegateAsyncProcessor((Exchange exchange) -> {
+ LOG.trace("Creating new subsegment for {} - EIP {}", defName, target);
+ Subsegment subsegment = AWSXRay.beginSubsegment(defName);
+ try {
+ LOG.trace("Processing EIP {}", target);
+ target.process(exchange);
+ } catch (Exception ex) {
+ LOG.trace("Handling exception thrown by invoked EIP {}", target);
+ subsegment.addException(ex);
+ throw ex;
+ } finally {
+ LOG.trace("Closing down subsegment for {}", defName);
+ subsegment.close();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/NoopTracingStrategy.java b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/NoopTracingStrategy.java
new file mode 100644
index 0000000..90f5b9c
--- /dev/null
+++ b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/NoopTracingStrategy.java
@@ -0,0 +1,32 @@
+/**
+ * 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.aws.xray;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Processor;
+import org.apache.camel.model.ProcessorDefinition;
+import org.apache.camel.processor.DelegateAsyncProcessor;
+import org.apache.camel.spi.InterceptStrategy;
+
+public class NoopTracingStrategy implements InterceptStrategy {
+
+ @Override
+ public Processor wrapProcessorInInterceptors(CamelContext camelContext, ProcessorDefinition<?> processorDefinition,
+ Processor target, Processor nextTarget) throws Exception {
+ return new DelegateAsyncProcessor(target);
+ }
+}
diff --git a/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/TraceAnnotatedTracingStrategy.java b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/TraceAnnotatedTracingStrategy.java
new file mode 100644
index 0000000..a8a7491
--- /dev/null
+++ b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/TraceAnnotatedTracingStrategy.java
@@ -0,0 +1,93 @@
+/**
+ * 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.aws.xray;
+
+import com.amazonaws.xray.AWSXRay;
+import com.amazonaws.xray.entities.Subsegment;
+import java.lang.annotation.Annotation;
+import java.lang.invoke.MethodHandles;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.component.bean.BeanProcessor;
+import org.apache.camel.model.BeanDefinition;
+import org.apache.camel.model.ProcessDefinition;
+import org.apache.camel.model.ProcessorDefinition;
+import org.apache.camel.processor.DelegateAsyncProcessor;
+import org.apache.camel.processor.DelegateSyncProcessor;
+import org.apache.camel.spi.InterceptStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TraceAnnotatedTracingStrategy implements InterceptStrategy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Override
+ public Processor wrapProcessorInInterceptors(CamelContext camelContext,
+ ProcessorDefinition<?> processorDefinition,
+ Processor target, Processor nextTarget)
+ throws Exception {
+
+ Class<?> processorClass = processorDefinition.getClass();
+
+ if (processorDefinition instanceof BeanDefinition) {
+ BeanProcessor beanProcessor = (BeanProcessor) nextTarget;
+ processorClass = beanProcessor.getBean().getClass();
+ } else if (processorDefinition instanceof ProcessDefinition) {
+ DelegateSyncProcessor syncProcessor = (DelegateSyncProcessor) nextTarget;
+ processorClass = syncProcessor.getProcessor().getClass();
+ }
+
+ if (!processorClass.isAnnotationPresent(XRayTrace.class)) {
+ LOG.trace("{} does not contain an @Trace annotation. Skipping interception",
+ processorClass.getSimpleName());
+ return new DelegateAsyncProcessor(target);
+ }
+
+ LOG.trace("Wrapping process definition {} of target {} in order for recording its trace",
+ processorDefinition, processorClass);
+
+ Annotation annotation = processorClass.getAnnotation(XRayTrace.class);
+ XRayTrace trace = (XRayTrace)annotation;
+
+ String metricName = trace.metricName();
+
+ if ("".equals(metricName)) {
+ metricName = processorClass.getSimpleName();
+ }
+
+ final Class<?> type = processorClass;
+ final String name = metricName;
+
+ return new DelegateAsyncProcessor((Exchange exchange) -> {
+ LOG.trace("Creating new subsegment for {} of type {} - EIP {}", name, type, target);
+ Subsegment subsegment = AWSXRay.beginSubsegment(name);
+ try {
+ LOG.trace("Processing EIP {}", target);
+ target.process(exchange);
+ } catch (Exception ex) {
+ LOG.trace("Handling exception thrown by invoked EIP {}", target);
+ subsegment.addException(ex);
+ throw ex;
+ } finally {
+ LOG.trace("Closing down subsegment for {}", name);
+ subsegment.close();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/XRayTrace.java b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/XRayTrace.java
new file mode 100644
index 0000000..8a05857
--- /dev/null
+++ b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/XRayTrace.java
@@ -0,0 +1,37 @@
+/**
+ * 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.aws.xray;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a class for being a target for tracing via Camel's AWS XRay tracer. The target class
+ * has thus to be either a Camel {@link org.apache.camel.Processor Processor} or a
+ * <code>.bean(...)</code> invoked class.
+ * <p/>
+ * The <em>metricName</em> argument allows to define a custom name visible in the resulting AWS XRay
+ * trace. If none is defined the simple class name of the respective class will be used.
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface XRayTrace {
+
+ String metricName() default "";
+}
diff --git a/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/XRayTracer.java b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/XRayTracer.java
new file mode 100644
index 0000000..f213b10
--- /dev/null
+++ b/components/camel-aws-xray/src/main/java/org/apache/camel/component/aws/xray/XRayTracer.java
@@ -0,0 +1,351 @@
+/**
+ * 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.aws.xray;
+
+import com.amazonaws.xray.AWSXRay;
+import com.amazonaws.xray.entities.Segment;
+import com.amazonaws.xray.entities.Subsegment;
+import com.amazonaws.xray.entities.TraceID;
+import java.lang.invoke.MethodHandles;
+import java.util.EventObject;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.Exchange;
+import org.apache.camel.Route;
+import org.apache.camel.StaticService;
+import org.apache.camel.management.event.ExchangeSendingEvent;
+import org.apache.camel.management.event.ExchangeSentEvent;
+import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.spi.InterceptStrategy;
+import org.apache.camel.spi.RoutePolicy;
+import org.apache.camel.spi.RoutePolicyFactory;
+import org.apache.camel.support.EventNotifierSupport;
+import org.apache.camel.support.RoutePolicySupport;
+import org.apache.camel.support.ServiceSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.ServiceHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * To use AWS XRay with Camel setup this {@link XRayTracer} in your Camel application.
+ * <p/>
+ * This class uses a {@link org.apache.camel.spi.RoutePolicy} as well as a {@link
+ * org.apache.camel.spi.EventNotifier} internally to manage the creation and termination of AWS XRay
+ * {@link Segment Segments} and {@link Subsegment Subsegments} once an exchange was created,
+ * forwarded or closed in order to allow monitoring the lifetime metrics of the exchange.
+ * <p/>
+ * A {@link InterceptStrategy} is used in order to track invocations and durations of EIP patterns
+ * used in processed routes. If no strategy is passed while configuration via {@link
+ * #setTracingStrategy(InterceptStrategy)}, a {@link NoopTracingStrategy} will be used by default
+ * which will not monitor any invocations at all.
+ * <p/>
+ * By default every invoked route will be tracked by AWS XRay. If certain routes shell not be
+ * tracked {@link #addExcludePattern(String)} and {@link #setExcludePatterns(Set)} can be used to
+ * provide the <em>routeId</em> of the routes to exclude from monitoring.
+ */
+public class XRayTracer extends ServiceSupport implements RoutePolicyFactory, StaticService, CamelContextAware {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /** Header value kept in the message of the exchange **/
+ public static final String XRAY_TRACE_ID = "Camel-AWS-XRay-Trace-ID";
+ /** Exchange property for passing a segment between threads **/
+ private static final String CURRENT_SEGMENT = "CAMEL_PROPERTY_AWS_XRAY_CURRENT_SEGMENT";
+
+ private final XRayEventNotifier eventNotifier = new XRayEventNotifier();
+ private CamelContext camelContext;
+
+ private Set<String> excludePatterns = new HashSet<>();
+ private InterceptStrategy tracingStrategy;
+
+ @Override
+ public void setCamelContext(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
+ @Override
+ public CamelContext getCamelContext() {
+ return this.camelContext;
+ }
+
+ @Override
+ public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, RouteDefinition route) {
+ init(camelContext);
+ return new XRayRoutePolicy(routeId);
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ ObjectHelper.notNull(camelContext, "CamelContext", this);
+
+ camelContext.getManagementStrategy().addEventNotifier(eventNotifier);
+ if (!camelContext.getRoutePolicyFactories().contains(this)) {
+ camelContext.addRoutePolicyFactory(this);
+ }
+
+ if (null == tracingStrategy) {
+ LOG.info("No tracing strategy available. Defaulting to no-op strategy");
+ tracingStrategy = new NoopTracingStrategy();
+ }
+
+ camelContext.addInterceptStrategy(tracingStrategy);
+
+ LOG.debug("Starting XRay tracer");
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ // stop event notifier
+ camelContext.getManagementStrategy().removeEventNotifier(eventNotifier);
+ ServiceHelper.stopAndShutdownService(eventNotifier);
+
+ camelContext.getRoutePolicyFactories().remove(this);
+ LOG.debug("XRay tracer stopped");
+ }
+
+ /**
+ * Initializes this AWS XRay tracer implementation as service within the Camel environment.
+ *
+ * @param camelContext The context to register this tracer as service with
+ */
+ public void init(CamelContext camelContext) {
+ if (!camelContext.hasService(this)) {
+ try {
+ LOG.debug("Initializing XRay tracer");
+ // start this service eager so we init before Camel is starting up
+ camelContext.addService(this, true, true);
+ } catch (Exception e) {
+ throw ObjectHelper.wrapRuntimeCamelException(e);
+ }
+ }
+ }
+
+ /**
+ * Returns the currently used tracing strategy which is responsible for tracking invoked EIP or
+ * beans.
+ *
+ * @return The currently used tracing strategy
+ */
+ public InterceptStrategy getTracingStrategy() {
+ return tracingStrategy;
+ }
+
+ /**
+ * Specifies the instance responsible for tracking invoked EIP and beans with AWS XRay.
+ *
+ * @param traceingStrategy The instance which tracks invoked EIP and beans
+ */
+ public void setTracingStrategy(InterceptStrategy traceingStrategy) {
+ this.tracingStrategy = traceingStrategy;
+ }
+
+ /**
+ * Returns the set of currently excluded routes. Any route ID specified in the returned set will
+ * not be monitored by this AWS XRay tracer implementation.
+ *
+ * @return The IDs of the currently excluded routes for which no tracking will be performed
+ */
+ public Set<String> getExcludePatterns() {
+ return this.excludePatterns;
+ }
+
+ /**
+ * Excludes all of the routes matching any of the contained routeIds within the given argument
+ * from tracking by this tracer implementation. Excluded routes will not appear within the AWS
+ * XRay monitoring.
+ *
+ * @param excludePatterns A set of routeIds which should not be tracked by this tracer
+ */
+ public void setExcludePatterns(Set<String> excludePatterns) {
+ this.excludePatterns = excludePatterns;
+ }
+
+ /**
+ * Adds an exclude pattern that will disable tracing for Camel messages that matches the pattern.
+ *
+ * @param pattern The pattern such as route id, endpoint url
+ */
+ public void addExcludePattern(String pattern) {
+ excludePatterns.add(pattern);
+ }
+
+ private boolean isExcluded(String routeId) {
+ // check for a defined routeId
+ if (!excludePatterns.isEmpty()) {
+ for (String pattern : excludePatterns) {
+ if (pattern.equals(routeId)) {
+ LOG.debug("Ignoring route with ID {}", routeId);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Custom camel event handler that will create a new {@link Subsegment XRay subsegment} in case
+ * the current exchange is forwarded via <code>.to(someEndpoint)</code> to some endpoint and
+ * accordingly closes the subsegment if the execution returns.
+ * <p/>
+ * Note that AWS XRay is designed to manage {@link Segment segments} and {@link Subsegment
+ * subsegments} within a {@link ThreadLocal} context. Forwarding the exchange to a <em>SEDA</em>
+ * endpoint will thus copy over the exchange to a new thread, though any available segment
+ * information collected by AWS XRay will not be available within that new thread!
+ * <p/>
+ * As {@link ExchangeSendingEvent} and {@link ExchangeSentEvent} both are executed within the
+ * invoking thread (in contrast to {@link org.apache.camel.management.event.ExchangeCreatedEvent
+ * ExchangeCreatedEvent} and {@link org.apache.camel.management.event.ExchangeCompletedEvent
+ * ExchangeCompletedEvent} which both run in the context of the spawned thread), adding further
+ * subsegments by this {@link org.apache.camel.spi.EventNotifier EventNotifier} implementation
+ * should be safe.
+ */
+ private final class XRayEventNotifier extends EventNotifierSupport {
+
+ @Override
+ public void notify(EventObject event) throws Exception {
+
+ if (event instanceof ExchangeSendingEvent) {
+ ExchangeSendingEvent ese = (ExchangeSendingEvent) event;
+ LOG.trace("-> {} - target: {} (routeId: {})",
+ event.getClass().getSimpleName(), ese.getEndpoint(),
+ ese.getExchange().getFromRouteId());
+
+ if (Thread.currentThread().getName().contains("Multicast")) {
+ // copy the segment from the exchange to the thread (local) context
+ Segment segment = (Segment)ese.getExchange().getProperty(CURRENT_SEGMENT);
+ LOG.trace("Copying over segment {}/{} from exchange received from {} to exchange processing {}",
+ segment.getId(), segment.getName(), ese.getExchange().getFromEndpoint(),
+ ese.getEndpoint());
+ AWSXRay.setTraceEntity(segment);
+ }
+
+ if (AWSXRay.getCurrentSegmentOptional().isPresent()) {
+ String endpointName = ese.getEndpoint().getEndpointKey();
+ // AWS XRay does only allow a certain set of characters to appear within a name
+ // Allowed characters: a-z, A-Z, 0-9, _, ., :, /, %, &, #, =, +, \, -, @
+ endpointName = endpointName.replaceAll("://", "_");
+ endpointName = endpointName.replaceAll("\\?", "&");
+ Subsegment subsegment = AWSXRay.beginSubsegment("SendingTo_" + endpointName);
+ LOG.trace("Creating new subsegment with ID {} and name {}",
+ subsegment.getId(), subsegment.getName());
+ } else {
+ LOG.trace("Ignoring creation of XRay subsegment as no segment exists in the current thread");
+ }
+
+ } else if (event instanceof ExchangeSentEvent) {
+ ExchangeSentEvent ese = (ExchangeSentEvent) event;
+ LOG.trace("-> {} - target: {} (routeId: {})",
+ event.getClass().getSimpleName(), ese.getEndpoint(), ese.getExchange().getFromRouteId());
+
+ if (AWSXRay.getCurrentSubsegmentOptional().isPresent()) {
+ Subsegment subsegment = AWSXRay.getCurrentSubsegment();
+ subsegment.close();
+ LOG.trace("Closing down subsegment with ID {} and name {}",
+ subsegment.getId(), subsegment.getName());
+ }
+ } else {
+ LOG.trace("Received event {} from source {}", event, event.getSource());
+ }
+ }
+
+ @Override
+ public boolean isEnabled(EventObject event) {
+ // listen for either when an exchange invoked an other endpoint
+ return event instanceof ExchangeSendingEvent
+ || event instanceof ExchangeSentEvent;
+ }
+ }
+
+ /**
+ * A custom {@link org.apache.camel.spi.RoutePolicy RoutePolicy} implementation that will create
+ * a new AWS XRay {@link Segment} once a new exchange is being created and the current thread
+ * does not know of an active segment yet. In case the exchange was forwarded within the same
+ * thread (i.e. by forwarding to a direct endpoint via <code>.to("direct:...)</code>) and a
+ * previous exchange already created a {@link Segment} this policy will add a new {@link
+ * Subsegment} for the created exchange to the trace.
+ * <p/>
+ * This policy will also manage the termination of created {@link Segment Segments} and {@link
+ * Subsegment Subsegments}.
+ * <p/>
+ * As AWS XRay is designed to manage {@link Segment Segments} in a {@link ThreadLocal} context
+ * this policy will create a new segment for each forward to a new thread i.e. by sending the
+ * exchange to a <em>SEDA</em> endpoint.
+ */
+ private final class XRayRoutePolicy extends RoutePolicySupport {
+
+ private String routeId;
+
+ XRayRoutePolicy(String routeId) {
+ this.routeId = routeId;
+ }
+
+ @Override
+ public void onExchangeBegin(Route route, Exchange exchange) {
+ // kicks in after a seda-thread was created. The new thread has the control
+ if (isExcluded(route.getId())) {
+ return;
+ }
+
+ LOG.trace("=> RoutePolicy-Begin: Route: {} - RouteId: {}", routeId, route.getId());
+
+ TraceID traceID;
+ if (exchange.getIn().getHeaders().containsKey(XRAY_TRACE_ID)) {
+ traceID = TraceID.fromString(exchange.getIn().getHeader(XRAY_TRACE_ID, String.class));
+ } else {
+ traceID = new TraceID();
+ exchange.getIn().setHeader(XRAY_TRACE_ID, traceID.toString());
+ }
+
+ if (!AWSXRay.getCurrentSegmentOptional().isPresent()) {
+ Segment segment = AWSXRay.beginSegment(route.getId());
+ segment.setTraceId(traceID);
+ LOG.trace("Created new XRay segment {} with name {}",
+ segment.getId(), segment.getName());
+ exchange.setProperty(CURRENT_SEGMENT, segment);
+ } else {
+ Subsegment subsegment = AWSXRay.beginSubsegment(route.getId());
+ LOG.trace("Created new XRay subsegment {} with name {}",
+ subsegment.getId(), subsegment.getName());
+ }
+ }
+
+ @Override
+ public void onExchangeDone(Route route, Exchange exchange) {
+ // kicks in before the seda-thread is terminated. Control is still in the seda-thread
+ if (isExcluded(route.getId())) {
+ return;
+ }
+
+ LOG.trace("=> RoutePolicy-Done: Route: {} - RouteId: {}", routeId, route.getId());
+
+ if (AWSXRay.getCurrentSubsegmentOptional().isPresent()) {
+ Subsegment subsegment = AWSXRay.getCurrentSubsegment();
+ subsegment.close();
+ LOG.trace("Closing down Subsegment {} with name {}",
+ subsegment.getId(), subsegment.getName());
+ } else if (AWSXRay.getCurrentSegmentOptional().isPresent()) {
+ Segment segment = AWSXRay.getCurrentSegment();
+ segment.close();
+ LOG.trace("Closing down Segment {} with name {}",
+ segment.getId(), segment.getName());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ABCRouteTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ABCRouteTest.java
new file mode 100644
index 0000000..713a58db
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ABCRouteTest.java
@@ -0,0 +1,80 @@
+/**
+ * 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.aws.xray;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class ABCRouteTest extends CamelAwsXRayTestSupport {
+
+ public ABCRouteTest() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_direct_a")
+ .withSubsegment(TestDataBuilder.createSubsegment("a")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_b"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_c"))
+ )
+ )
+ )
+ .withSegment(TestDataBuilder.createSegment("b"))
+ .withSegment(TestDataBuilder.createSegment("c")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_log_test"))
+ )
+ .withSegment(TestDataBuilder.createSegment("d"))
+ );
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:start", "Hello");
+
+ verify();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").routeId("start")
+ .wireTap("seda:d")
+ .to("direct:a");
+
+ from("direct:a").routeId("a")
+ .log("routing at ${routeId}")
+ .to("seda:b")
+ .delay(2000)
+ .to("seda:c")
+ .log("End of routing");
+
+ from("seda:b").routeId("b")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(1000,2000)}"));
+
+ from("seda:c").routeId("c")
+ .to("log:test")
+ .delay(simple("${random(0,100)}"));
+
+ from("seda:d").routeId("d")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(10,50)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/BeanTracingTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/BeanTracingTest.java
new file mode 100644
index 0000000..943e545
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/BeanTracingTest.java
@@ -0,0 +1,107 @@
+/**
+ * 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.aws.xray;
+
+import com.amazonaws.xray.AWSXRay;
+import org.apache.camel.Body;
+import org.apache.camel.Exchange;
+import org.apache.camel.Handler;
+import org.apache.camel.Processor;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.InterceptStrategy;
+import org.junit.Test;
+
+public class BeanTracingTest extends CamelAwsXRayTestSupport {
+
+ public BeanTracingTest() {
+ super(
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_otherRoute"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_mock_end"))
+ .withAnnotation("body", "HELLO")
+ .withMetadata("originBody", "Hello")
+ )
+ .withSegment(TestDataBuilder.createSegment("otherRoute")
+ .withSubsegment(TestDataBuilder.createSubsegment("processor"))
+ )
+ );
+ }
+
+ @Override
+ protected InterceptStrategy getTracingStrategy() {
+ return new TraceAnnotatedTracingStrategy();
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ MockEndpoint mockEndpoint = context.getEndpoint("mock:end", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+ mockEndpoint.expectedBodiesReceived("HELLO");
+ mockEndpoint.expectedHeaderReceived("TEST", "done");
+
+ template.requestBody("direct:start", "Hello");
+
+ mockEndpoint.assertIsSatisfied();
+
+ verify();
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").routeId("start")
+ .log("start has been called")
+ .bean(TraceBean.class)
+ .delay(simple("${random(1000,2000)}"))
+ .to("seda:otherRoute")
+ .to("mock:end");
+
+ from("seda:otherRoute").routeId("otherRoute")
+ .log("otherRoute has been called")
+ .process(new CustomProcessor())
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+
+ @XRayTrace
+ public static class TraceBean {
+
+ @Handler
+ public String convertBocyToUpperCase(@Body String body) {
+ String converted = body.toUpperCase();
+ AWSXRay.getCurrentSegment().putAnnotation("body", converted);
+ AWSXRay.getCurrentSegment().putMetadata("originBody", body);
+ return converted;
+ }
+ }
+
+ @XRayTrace(metricName = "processor")
+ public static class CustomProcessor implements Processor {
+
+ @Override
+ public void process(Exchange exchange) throws Exception {
+ exchange.getIn().setHeader("TEST", "done");
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/CamelAwsXRayTestSupport.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/CamelAwsXRayTestSupport.java
new file mode 100644
index 0000000..0cafc0a
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/CamelAwsXRayTestSupport.java
@@ -0,0 +1,93 @@
+/**
+ * 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.aws.xray;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.camel.CamelContext;
+import org.apache.camel.LoggingLevel;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestTrace;
+import org.apache.camel.processor.interceptor.Tracer;
+import org.apache.camel.spi.InterceptStrategy;
+import org.apache.camel.test.junit4.CamelTestSupport;
+import org.junit.Rule;
+
+
+public class CamelAwsXRayTestSupport extends CamelTestSupport {
+
+ private List<TestTrace> testData;
+
+ @Rule
+ public FakeAWSDaemon socketListener = new FakeAWSDaemon();
+
+ public CamelAwsXRayTestSupport(TestTrace... testData) {
+ this.testData = Arrays.asList(testData);
+ }
+
+ @Override
+ protected void postProcessTest() throws Exception {
+ super.postProcessTest();
+ socketListener.getReceivedData().clear();
+ }
+
+ @Override
+ protected void resetMocks() {
+ super.resetMocks();
+ }
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ CamelContext context = super.createCamelContext();
+
+ context.setTracing(true);
+ final Tracer tracer = new Tracer();
+ tracer.getDefaultTraceFormatter().setShowBody(false);
+ tracer.setLogLevel(LoggingLevel.INFO);
+ context.getInterceptStrategies().add(tracer);
+
+ XRayTracer xRayTracer = new XRayTracer();
+ xRayTracer.setCamelContext(context);
+ xRayTracer.setTracingStrategy(getTracingStrategy());
+ xRayTracer.setExcludePatterns(getExcludePatterns());
+
+ xRayTracer.init(context);
+
+ return context;
+ }
+
+ protected InterceptStrategy getTracingStrategy() {
+ return new NoopTracingStrategy();
+ }
+
+ protected Set<String> getExcludePatterns() {
+ return new HashSet<>();
+ }
+
+ protected void verify() {
+ try {
+ // give the socket listener a bit time to receive the data and transform it to Java objects
+ Thread.sleep(500);
+ } catch (InterruptedException iEx) {
+ // ignore
+ }
+ Map<String, TestTrace> receivedData = socketListener.getReceivedData();
+ TestUtils.checkData(receivedData, testData);
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ClientRecipientListRouteTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ClientRecipientListRouteTest.java
new file mode 100644
index 0000000..225602c
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ClientRecipientListRouteTest.java
@@ -0,0 +1,66 @@
+/**
+ * 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.aws.xray;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class ClientRecipientListRouteTest extends CamelAwsXRayTestSupport {
+
+ public ClientRecipientListRouteTest() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_a"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_b"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_c"))
+ )
+ .withSegment(TestDataBuilder.createSegment("a"))
+ .withSegment(TestDataBuilder.createSegment("b"))
+ .withSegment(TestDataBuilder.createSegment("c"))
+ );
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:start", "Hello");
+
+ verify();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").routeId("start")
+ .recipientList(constant("seda:a,seda:b,seda:c"));
+
+ from("seda:a").routeId("a")
+ .log("routing at ${routeId}");
+
+ from("seda:b").routeId("b")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(1000,2000)}"));
+
+ from("seda:c").routeId("c")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(0,100)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/EIPTracingTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/EIPTracingTest.java
new file mode 100644
index 0000000..4b6ad69
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/EIPTracingTest.java
@@ -0,0 +1,116 @@
+/**
+ * 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.aws.xray;
+
+import com.amazonaws.xray.AWSXRay;
+import org.apache.camel.Body;
+import org.apache.camel.Exchange;
+import org.apache.camel.Handler;
+import org.apache.camel.Processor;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.InterceptStrategy;
+import org.junit.Test;
+
+public class EIPTracingTest extends CamelAwsXRayTestSupport {
+
+ public EIPTracingTest() {
+ super(
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("log"))
+ .withSubsegment(TestDataBuilder.createSubsegment("bean"))
+ .withSubsegment(TestDataBuilder.createSubsegment("delay")
+ .withSubsegment(TestDataBuilder.createSubsegment("to")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_otherRoute"))
+ )
+ .withSubsegment(TestDataBuilder.createSubsegment("to")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_mock_end"))
+ )
+ )
+ .withAnnotation("body", "HELLO")
+ .withMetadata("originBody", "Hello")
+ )
+ .withSegment(TestDataBuilder.createSegment("otherRoute")
+ .withSubsegment(TestDataBuilder.createSubsegment("log"))
+ .withSubsegment(TestDataBuilder.createSubsegment("process"))
+ .withSubsegment(TestDataBuilder.createSubsegment("delay"))
+ )
+ );
+ }
+
+ @Override
+ protected InterceptStrategy getTracingStrategy() {
+ return new EIPTracingStrategy();
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ MockEndpoint mockEndpoint = context.getEndpoint("mock:end", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+ mockEndpoint.expectedBodiesReceived("HELLO");
+ mockEndpoint.expectedHeaderReceived("TEST", "done");
+
+ template.requestBody("direct:start", "Hello");
+
+ mockEndpoint.assertIsSatisfied();
+
+ verify();
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").routeId("start")
+ .log("start has been called")
+ .bean(TraceBean.class)
+ .delay(simple("${random(1000,2000)}"))
+ .to("seda:otherRoute")
+ .to("mock:end");
+
+ from("seda:otherRoute").routeId("otherRoute")
+ .log("otherRoute has been called")
+ .process(new CustomProcessor())
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+
+ @XRayTrace
+ public static class TraceBean {
+
+ @Handler
+ public String convertBocyToUpperCase(@Body String body) {
+ String converted = body.toUpperCase();
+ AWSXRay.getCurrentSegment().putAnnotation("body", converted);
+ AWSXRay.getCurrentSegment().putMetadata("originBody", body);
+ return converted;
+ }
+ }
+
+ @XRayTrace(metricName = "processor")
+ public static class CustomProcessor implements Processor {
+
+ @Override
+ public void process(Exchange exchange) throws Exception {
+ exchange.getIn().setHeader("TEST", "done");
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ErrorHandlingTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ErrorHandlingTest.java
new file mode 100644
index 0000000..dd57dd8
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ErrorHandlingTest.java
@@ -0,0 +1,156 @@
+/**
+ * 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.aws.xray;
+
+import java.lang.invoke.MethodHandles;
+import org.apache.camel.Body;
+import org.apache.camel.Exchange;
+import org.apache.camel.Handler;
+import org.apache.camel.LoggingLevel;
+import org.apache.camel.Processor;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.InterceptStrategy;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ErrorHandlingTest extends CamelAwsXRayTestSupport {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // FIXME: check why processors invoked in onRedelivery do not generate a subsegment
+ public ErrorHandlingTest() {
+ super(
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+// .withSubsegment(TestDataBuilder.createSubsegment("ExceptionRetryProcessor"))
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+// .withSubsegment(TestDataBuilder.createSubsegment("ExceptionRetryProcessor"))
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+// .withSubsegment(TestDataBuilder.createSubsegment("ExceptionRetryProcessor"))
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_otherRoute"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_mock_end"))
+ )
+ .withSegment(TestDataBuilder.createSegment("otherRoute"))
+ );
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+
+ onException(Exception.class)
+ .process(new ExceptionProcessor())
+ .maximumRedeliveries(3)
+ .redeliveryDelay(200)
+ .useExponentialBackOff()
+ .backOffMultiplier(1.5D)
+ .onRedelivery(new ExceptionRetryProcessor())
+ .handled(true)
+ .log(LoggingLevel.WARN, "Caught error while performing task. Reason: ${exception.message} Stacktrace: ${exception.stacktrace}")
+ .end();
+
+ from("direct:start").routeId("start")
+ .log("start has been called")
+ .bean(TraceBean.class)
+ .delay(simple("${random(1000,2000)}"))
+ .to("seda:otherRoute")
+ .to("mock:end");
+
+ from("seda:otherRoute").routeId("otherRoute")
+ .log("otherRoute has been called")
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+
+ @Override
+ protected InterceptStrategy getTracingStrategy() {
+ return new TraceAnnotatedTracingStrategy();
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ MockEndpoint mockEndpoint = context.getEndpoint("mock:end", MockEndpoint.class);
+ mockEndpoint.expectedMessageCount(1);
+ mockEndpoint.expectedBodiesReceived("HELLO");
+
+ template.requestBody("direct:start", "Hello");
+
+ mockEndpoint.assertIsSatisfied();
+
+ verify();
+ }
+
+ @XRayTrace
+ public static class TraceBean {
+
+ private static int COUNTER = 0;
+ @Handler
+ public String convertBodyToUpperCase(@Body String body) throws Exception {
+ String converted = body.toUpperCase();
+ if (COUNTER < 3) {
+ COUNTER++;
+ throw new Exception("test");
+ }
+ return converted;
+ }
+
+ @Override
+ public String toString() {
+ return "TraceBean";
+ }
+ }
+
+ @XRayTrace
+ public static class ExceptionProcessor implements Processor {
+
+ @Override
+ public void process(Exchange exchange) throws Exception {
+ Exception ex = (Exception)exchange.getProperties().get(Exchange.EXCEPTION_CAUGHT);
+ LOG.debug("Processing caught exception {}", ex.getLocalizedMessage());
+ exchange.getIn().getHeaders().put("HandledError",ex.getLocalizedMessage());
+ }
+
+ @Override
+ public String toString() {
+ return "ExceptionProcessor";
+ }
+ }
+
+ @XRayTrace
+ public static class ExceptionRetryProcessor implements Processor {
+
+ @Override
+ public void process(Exchange exchange) throws Exception {
+ Exception ex = (Exception)exchange.getProperties().get(Exchange.EXCEPTION_CAUGHT);
+ LOG.debug(">> Attempting redelivery of handled exception {} with message: {}",
+ ex.getClass().getSimpleName(), ex.getLocalizedMessage());
+ }
+
+ @Override
+ public String toString() {
+ return "ExceptionRetryProcessor";
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ErrorTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ErrorTest.java
new file mode 100644
index 0000000..b7d625d
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/ErrorTest.java
@@ -0,0 +1,138 @@
+/**
+ * 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.aws.xray;
+
+import java.lang.invoke.MethodHandles;
+import org.apache.camel.Body;
+import org.apache.camel.Exchange;
+import org.apache.camel.Handler;
+import org.apache.camel.LoggingLevel;
+import org.apache.camel.Processor;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.InterceptStrategy;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ErrorTest extends CamelAwsXRayTestSupport {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ // FIXME: check why processors invoked in onRedelivery do not generate a subsegment
+ public ErrorTest() {
+ super(
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+ .withSubsegment(TestDataBuilder.createSubsegment("TraceBean"))
+ .withSubsegment(TestDataBuilder.createSubsegment("ExceptionProcessor"))
+ )
+ );
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+
+ onException(Exception.class)
+ .process(new ExceptionProcessor())
+ .maximumRedeliveries(3)
+ .redeliveryDelay(200)
+ .useExponentialBackOff()
+ .backOffMultiplier(1.5D)
+ .onRedelivery(new ExceptionRetryProcessor())
+ .handled(true)
+ .log(LoggingLevel.WARN, "Caught error while performing task. Reason: ${exception.message} Stacktrace: ${exception.stacktrace}")
+ .end();
+
+ from("direct:start").routeId("start")
+ .log("start has been called")
+ .bean(TraceBean.class)
+ .delay(simple("${random(1000,2000)}"))
+ .to("seda:otherRoute")
+ .to("mock:end");
+
+ from("seda:otherRoute").routeId("otherRoute")
+ .log("otherRoute has been called")
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+
+ @Override
+ protected InterceptStrategy getTracingStrategy() {
+ return new TraceAnnotatedTracingStrategy();
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:start", "Hello");
+
+ verify();
+ }
+
+ @XRayTrace
+ public static class TraceBean {
+
+ @Handler
+ public String convertBodyToUpperCase(@Body String body) throws Exception {
+ throw new Exception("test");
+ }
+
+ @Override
+ public String toString() {
+ return "TraceBean";
+ }
+ }
+
+ @XRayTrace
+ public static class ExceptionProcessor implements Processor {
+
+ @Override
+ public void process(Exchange exchange) throws Exception {
+ Exception ex = (Exception)exchange.getProperties().get(Exchange.EXCEPTION_CAUGHT);
+ LOG.debug("Processing caught exception {}", ex.getLocalizedMessage());
+ exchange.getIn().getHeaders().put("HandledError",ex.getLocalizedMessage());
+ }
+
+ @Override
+ public String toString() {
+ return "ExceptionProcessor";
+ }
+ }
+
+ @XRayTrace
+ public static class ExceptionRetryProcessor implements Processor {
+
+ @Override
+ public void process(Exchange exchange) throws Exception {
+ Exception ex = (Exception)exchange.getProperties().get(Exchange.EXCEPTION_CAUGHT);
+ LOG.debug(">> Attempting redelivery of handled exception {} with message: {}",
+ ex.getClass().getSimpleName(), ex.getLocalizedMessage());
+ }
+
+ @Override
+ public String toString() {
+ return "ExceptionRetryProcessor";
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/FakeAWSDaemon.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/FakeAWSDaemon.java
new file mode 100644
index 0000000..f94951b
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/FakeAWSDaemon.java
@@ -0,0 +1,251 @@
+/**
+ * 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.aws.xray;
+
+import java.lang.invoke.MethodHandles;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestEntity;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestSegment;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestSubsegment;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestTrace;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.rules.ExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FakeAWSDaemon extends ExternalResource {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private Map<String, TestTrace> receivedTraces = Collections.synchronizedMap(new LinkedHashMap<>());
+ private UDPSocketListener socketListener = new UDPSocketListener(receivedTraces);
+ private ExecutorService executorService = Executors.newSingleThreadExecutor();
+
+ @Override
+ protected void before() throws Throwable {
+ LOG.info("Starting up Mock-AWS daemon");
+ executorService.submit(socketListener);
+ }
+
+ @Override
+ protected void after() {
+ LOG.info("Shutting down Mock-AWS daemon");
+ socketListener.close();
+ executorService.shutdown();
+ try {
+ if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ executorService.shutdownNow();
+ if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ LOG.error("Could not terminate UDP server");
+ }
+ }
+ } catch (InterruptedException iEx) {
+ executorService.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ Map<String, TestTrace> getReceivedData() {
+ LOG.trace("List of received data packages requested: {}", receivedTraces.size());
+ return receivedTraces;
+ }
+
+ private static class UDPSocketListener implements Runnable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private DatagramSocket serverSocket = null;
+ private Map<String, TestTrace> receivedTraces;
+ private volatile boolean done = false;
+
+ private UDPSocketListener(Map<String, TestTrace> receivedTraces) {
+ this.receivedTraces = receivedTraces;
+ }
+
+ @Override
+ public void run() {
+ try {
+ LOG.info("Starting UDP socket listening on port 2000");
+ serverSocket = new DatagramSocket(2000);
+
+ StringBuilder sb = new StringBuilder();
+ byte[] receiveData = new byte[8096];
+ while (!done) {
+ DatagramPacket receivedPacket = new DatagramPacket(receiveData, receiveData.length);
+ serverSocket.receive(receivedPacket);
+
+ LOG.debug("Receiving UDP data");
+ sb.append(new String(receivedPacket.getData()));
+
+ String _segment = null;
+ try {
+ String raw = sb.toString();
+ String[] segments = raw.split("\\n");
+ for (String segment : segments) {
+ _segment = segment;
+ LOG.trace("Processing received segment: {}", segment);
+ if (!"".equals(segment)) {
+ if (segment.contains("format") && segment.contains("version")) {
+ LOG.trace("Skipping format and version JSON");
+ } else {
+ LOG.trace("Converting segment {} to a Java object", segment);
+ JSONObject json = new JSONObject(segment);
+ String traceId = json.getString("trace_id");
+ TestTrace testTrace = receivedTraces.get(traceId);
+ if (null == testTrace) {
+ testTrace = new TestTrace();
+ }
+ testTrace.withSegment(convertData(json));
+ receivedTraces.put(traceId, testTrace);
+ }
+ sb.delete(0, segment.length());
+ if (sb.length() > 1 && sb.charAt(0) == '\n') {
+ sb.deleteCharAt(0);
+ }
+ }
+ }
+ LOG.trace("Item {} received. JSON content: {}, Raw: {}",
+ receivedTraces.size(), receivedTraces, raw);
+ } catch (JSONException jsonEx) {
+ LOG.warn("Could not convert segment " + _segment + " to a Java object", jsonEx);
+ }
+ }
+ } catch (SocketException sex) {
+ LOG.info("UDP socket closed");
+ } catch (Exception ex) {
+ LOG.warn("UDP socket failed due to " + ex.getLocalizedMessage(), ex);
+ }
+ }
+
+ private TestSegment convertData(JSONObject json) {
+ String name = json.getString("name");
+ double startTime = json.getDouble("start_time");
+ TestSegment segment = new TestSegment(name, startTime);
+ if (json.has("subsegments")) {
+ JSONArray jsonSubsegments = json.getJSONArray("subsegments");
+ List<TestSubsegment> subsegments = convertSubsegments(jsonSubsegments);
+ for (TestSubsegment subsegment : subsegments) {
+ segment.withSubsegment(subsegment);
+ }
+ }
+ addAnnotationsIfAvailable(segment, json);
+ addMetadataIfAvailable(segment, json);
+ return segment;
+ }
+
+ private List<TestSubsegment> convertSubsegments(JSONArray jsonSubsegments) {
+ List<TestSubsegment> subsegments = new ArrayList<>(jsonSubsegments.length());
+ for (int i = 0; i < jsonSubsegments.length(); i++) {
+ JSONObject jsonSubsegment = jsonSubsegments.getJSONObject(i);
+ subsegments.add(convertSubsegment(jsonSubsegment));
+ }
+ return subsegments;
+ }
+
+ private TestSubsegment convertSubsegment(JSONObject json) {
+ TestSubsegment subsegment = new TestSubsegment(json.getString("name"));
+ if (json.has("subsegments")) {
+ List<TestSubsegment> subsegments = convertSubsegments(json.getJSONArray("subsegments"));
+ for (TestSubsegment tss : subsegments) {
+ subsegment.withSubsegment(tss);
+ }
+ }
+ addAnnotationsIfAvailable(subsegment, json);
+ addMetadataIfAvailable(subsegment, json);
+ return subsegment;
+ }
+
+ private void addAnnotationsIfAvailable(TestEntity<?> entity, JSONObject json) {
+ if (json.has("annotations")) {
+ Map<String, Object> annotations = parseAnnotations(json.getJSONObject("annotations"));
+ for (String key : annotations.keySet()) {
+ entity.withAnnotation(key, annotations.get(key));
+ }
+ }
+ }
+
+ private void addMetadataIfAvailable(TestEntity<?> entity, JSONObject json) {
+ if (json.has("metadata")) {
+ Map<String, Map<String, Object>> metadata = parseMetadata(json.getJSONObject("metadata"));
+ for (String namespace : metadata.keySet()) {
+ for (String key : metadata.get(namespace).keySet()) {
+ entity.withMetadata(namespace, key, metadata.get(namespace).get(key));
+ }
+ }
+ }
+ }
+
+ private Map<String, Object> parseAnnotations(JSONObject json) {
+ /*
+ "annotations" : {
+ "test2" : 1,
+ "test3" : true,
+ "test1" : "test"
+ }
+ */
+ Map<String, Object> annotations = new LinkedHashMap<>(json.keySet().size());
+ for (String key : json.keySet()) {
+ annotations.put(key, json.get(key));
+ }
+ return annotations;
+ }
+
+ private Map<String, Map<String, Object>> parseMetadata(JSONObject json) {
+ /*
+ "metadata" : {
+ "default" : {
+ "meta1" : "meta1"
+ },
+ "customNamespace" : {
+ "meta2" : "meta2"
+ }
+ }
+ */
+ Map<String, Map<String, Object>> metadata = new LinkedHashMap<>(json.keySet().size());
+ for (String namespace : json.keySet()) {
+ JSONObject namespaceData = json.getJSONObject(namespace);
+ if (!metadata.containsKey(namespace)) {
+ metadata.put(namespace, new LinkedHashMap<>(namespaceData.keySet().size()));
+ }
+ for (String key : namespaceData.keySet()) {
+ metadata.get(namespace).put(key, namespaceData.get(key));
+ }
+ }
+ return metadata;
+ }
+
+ private void close() {
+ done = true;
+ if (null != serverSocket) {
+ LOG.info("Shutting down UDP socket");
+ serverSocket.close();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/MulticastParallelRouteTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/MulticastParallelRouteTest.java
new file mode 100644
index 0000000..d0b7e00
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/MulticastParallelRouteTest.java
@@ -0,0 +1,73 @@
+/**
+ * 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.aws.xray;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class MulticastParallelRouteTest extends CamelAwsXRayTestSupport {
+
+ public MulticastParallelRouteTest() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_a"))
+ )
+ .withSegment(TestDataBuilder.createSegment("a").inRandomOrder()
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_b"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_c"))
+ )
+ .withSegment(TestDataBuilder.createSegment("b"))
+ .withSegment(TestDataBuilder.createSegment("c")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_log_routing%20at%20$%7BrouteId%7D"))
+ )
+ );
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:start", "Hello");
+
+ verify();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").routeId("start")
+ .to("seda:a");
+
+ from("seda:a").routeId("a")
+ .log("routing at ${routeId}")
+ .multicast().parallelProcessing()
+ .to("seda:b", "seda:c")
+ .end()
+ .log("End of routing");
+
+ from("seda:b").routeId("b")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(1000,2000)}"));
+
+ from("seda:c").routeId("c")
+ .to("log:routing at ${routeId}")
+ .delay(simple("${random(0,100)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/MulticastRouteTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/MulticastRouteTest.java
new file mode 100644
index 0000000..e80261f
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/MulticastRouteTest.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.camel.component.aws.xray;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class MulticastRouteTest extends CamelAwsXRayTestSupport {
+
+ public MulticastRouteTest() {
+ super(
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("start")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_a"))
+ )
+ .withSegment(TestDataBuilder.createSegment("a")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_b"))
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_seda_c"))
+ )
+ .withSegment(TestDataBuilder.createSegment("b"))
+ .withSegment(TestDataBuilder.createSegment("c")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_log_routing%20at%20$%7BrouteId%7D"))
+ )
+ );
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:start", "Hello");
+
+ verify();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:start").routeId("start")
+ .to("seda:a");
+
+ from("seda:a").routeId("a")
+ .log("routing at ${routeId}")
+ .multicast()
+ .to("seda:b")
+ .to("seda:c")
+ .end()
+ .log("End of routing");
+
+ from("seda:b").routeId("b")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(1000,2000)}"));
+
+ from("seda:c").routeId("c")
+ .to("log:routing at ${routeId}")
+ .delay(simple("${random(0,100)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/Route2ConcurrentTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/Route2ConcurrentTest.java
new file mode 100644
index 0000000..906ce12
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/Route2ConcurrentTest.java
@@ -0,0 +1,75 @@
+/**
+ * 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.aws.xray;
+
+import java.util.concurrent.TimeUnit;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class Route2ConcurrentTest extends CamelAwsXRayTestSupport {
+
+ public Route2ConcurrentTest() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("foo"))
+ .withSegment(TestDataBuilder.createSegment("bar")),
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("foo"))
+ .withSegment(TestDataBuilder.createSegment("bar")),
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("foo"))
+ .withSegment(TestDataBuilder.createSegment("bar")),
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("foo"))
+ .withSegment(TestDataBuilder.createSegment("bar")),
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("foo"))
+ .withSegment(TestDataBuilder.createSegment("bar"))
+ );
+ }
+
+ @Test
+ public void testConcurrentInvocationsOfRoute() throws Exception {
+ NotifyBuilder notify = new NotifyBuilder(context).whenDone(10).create();
+
+ for (int i = 0; i < 5; i++) {
+ template.sendBody("seda:foo", "Hello World");
+ }
+
+ assertTrue(notify.matches(30, TimeUnit.SECONDS));
+
+ verify();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("seda:foo?concurrentConsumers=5").routeId("foo")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(1000,2000)}"))
+ .to("seda:bar");
+
+ from("seda:bar?concurrentConsumers=5").routeId("bar")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/RouteConcurrentTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/RouteConcurrentTest.java
new file mode 100644
index 0000000..209977f
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/RouteConcurrentTest.java
@@ -0,0 +1,61 @@
+/**
+ * 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.aws.xray;
+
+import java.util.concurrent.TimeUnit;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class RouteConcurrentTest extends CamelAwsXRayTestSupport {
+
+ public RouteConcurrentTest() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("foo"))
+ .withSegment(TestDataBuilder.createSegment("bar"))
+ );
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ NotifyBuilder notify = new NotifyBuilder(context).whenDone(2).create();
+
+ template.sendBody("seda:foo", "Hello World");
+
+ assertTrue(notify.matches(30, TimeUnit.SECONDS));
+
+ verify();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("seda:foo?concurrentConsumers=5").routeId("foo")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(1000,2000)}"))
+ .to("seda:bar");
+
+ from("seda:bar?concurrentConsumers=5").routeId("bar")
+ .log("routing at ${routeId}")
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/SpringAwsXRaySimpleRouteTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/SpringAwsXRaySimpleRouteTest.java
new file mode 100644
index 0000000..95ee026
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/SpringAwsXRaySimpleRouteTest.java
@@ -0,0 +1,77 @@
+/**
+ * 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.aws.xray;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.apache.camel.builder.NotifyBuilder;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestTrace;
+import org.apache.camel.test.spring.CamelSpringTestSupport;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+public class SpringAwsXRaySimpleRouteTest extends CamelSpringTestSupport {
+
+ @Rule
+ public FakeAWSDaemon socketListener = new FakeAWSDaemon();
+
+ @Override
+ protected AbstractApplicationContext createApplicationContext() {
+ return new ClassPathXmlApplicationContext("/org/apache/camel/aws/xray/AwsXRaySimpleRouteTest.xml");
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ NotifyBuilder notify = new NotifyBuilder(context).whenDone(5).create();
+
+ for (int i = 0; i < 5; i++) {
+ template.sendBody("seda:dude", "Hello World");
+ }
+
+ assertTrue(notify.matches(30, TimeUnit.SECONDS));
+
+ List<TestTrace> testData = Arrays.asList(
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("dude")
+ .withSubsegment(TestDataBuilder.createSubsegment("car"))
+ ),
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("dude")
+ .withSubsegment(TestDataBuilder.createSubsegment("car"))
+ ),
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("dude")
+ .withSubsegment(TestDataBuilder.createSubsegment("car"))
+ ),
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("dude")
+ .withSubsegment(TestDataBuilder.createSubsegment("car"))
+ ),
+ TestDataBuilder.createTrace()
+ .withSegment(TestDataBuilder.createSegment("dude")
+ .withSubsegment(TestDataBuilder.createSubsegment("car"))
+ )
+ );
+
+ Thread.sleep(2000);
+
+ TestUtils.checkData(socketListener.getReceivedData(), testData);
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TestDataBuilder.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TestDataBuilder.java
new file mode 100644
index 0000000..badd327
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TestDataBuilder.java
@@ -0,0 +1,210 @@
+/**
+ * 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.aws.xray;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+@SuppressWarnings({"WeakerAccess", "unchecked"})
+class TestDataBuilder {
+
+ static class TestTrace {
+
+ private boolean randomOrder = false;
+ private Set<TestSegment> segments = new TreeSet<>((TestSegment seg1, TestSegment seg2) -> {
+ if (seg1.equals(seg2)) {
+ return 0;
+ }
+ if (seg1.startTime != 0 && seg2.startTime != 0) {
+ if (seg1.startTime == seg2.startTime) {
+ return -1;
+ }
+ return seg1.startTime < seg2.startTime ? -1 : 1;
+ } else {
+ return 1;
+ }
+ });
+
+ public TestTrace withSegment(TestSegment segment) {
+ this.segments.add(segment);
+ return this;
+ }
+
+ public Set<TestSegment> getSegments() {
+ return segments;
+ }
+
+ public TestTrace inRandomOrder() {
+ randomOrder = true;
+ return this;
+ }
+
+ public boolean isRandomOrder() {
+ return randomOrder;
+ }
+ }
+
+ public static abstract class TestEntity<T> {
+ protected String name;
+ protected Map<String, Object> annotations = new LinkedHashMap<>();
+ protected Map<String, Map<String, Object>> metadata = new LinkedHashMap<>();
+ protected List<TestSubsegment> subsegments = new ArrayList<>();
+ protected boolean randomOrder = false;
+
+ protected TestEntity(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Map<String, Object> getAnnotations() {
+ return this.annotations;
+ }
+
+ public Map<String, Map<String, Object>> getMetadata() {
+ return metadata;
+ }
+
+ public List<TestSubsegment> getSubsegments() {
+ return subsegments;
+ }
+
+ public T withAnnotation(String name, Object value) {
+ this.annotations.put(name, value);
+ return (T)this;
+ }
+
+ public T withMetadata(String name, Object value) {
+ return this.withMetadata("default", name, value);
+ }
+
+ public T withMetadata(String namespace, String name, Object value) {
+ if (!this.metadata.containsKey(namespace)) {
+ this.metadata.put(namespace, new LinkedHashMap<>());
+ }
+ Map<String, Object> namespaceMap = this.metadata.get(namespace);
+ namespaceMap.put(name, value);
+ return (T)this;
+ }
+
+ public T withSubsegment(TestSubsegment subsegment) {
+ this.subsegments.add(subsegment);
+ return (T)this;
+ }
+
+ public T inRandomOrder() {
+ this.randomOrder = true;
+ return (T)this;
+ }
+
+ public boolean isRandomOrder() {
+ return randomOrder;
+ }
+
+ @Override
+ public String toString() {
+ String ret = this.getClass().getSimpleName() + "(name: " + name;
+
+ if (!subsegments.isEmpty()) {
+ ret += ", subsegments: [";
+ StringBuilder sb = new StringBuilder();
+ for (TestSubsegment sub : subsegments) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(sub);
+ }
+ ret += sb.toString()+"]";
+ }
+ if (!annotations.isEmpty()) {
+ ret += ", annotations: {";
+ StringBuilder sb = new StringBuilder();
+ for (String key: annotations.keySet()) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(key).append("->").append(annotations.get(key));
+ }
+ ret += sb.toString() + "}";
+ }
+ if (!metadata.isEmpty()) {
+ ret += ", metadata: {";
+ StringBuilder sb = new StringBuilder();
+ for (String namespace : metadata.keySet()) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(namespace).append(": [");
+ boolean first = true;
+ for (String key : metadata.get(namespace).keySet()) {
+ if (!first) {
+ sb.append(", ");
+ }
+ sb.append(key).append("->").append(metadata.get(namespace).get(key));
+ first = false;
+ }
+ sb.append("]");
+ }
+ ret += sb.toString() + "}";
+ }
+ ret += ")";
+ return ret;
+ }
+ }
+
+ static class TestSegment extends TestEntity<TestSegment> {
+ private double startTime;
+
+ public TestSegment(String name) {
+ super(name);
+ }
+
+ public TestSegment(String name, double startTime) {
+ this(name);
+ this.startTime = startTime;
+ }
+
+ public double getStartTime() {
+ return this.startTime;
+ }
+ }
+
+ static class TestSubsegment extends TestEntity<TestSubsegment> {
+
+ public TestSubsegment(String name) {
+ super(name);
+ }
+ }
+
+ public static TestTrace createTrace() {
+ return new TestTrace();
+ }
+
+ public static TestSegment createSegment(String name) {
+ return new TestSegment(name);
+ }
+
+ public static TestSubsegment createSubsegment(String name) {
+ return new TestSubsegment(name);
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TestUtils.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TestUtils.java
new file mode 100644
index 0000000..03f1f1c
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TestUtils.java
@@ -0,0 +1,161 @@
+/**
+ * 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.aws.xray;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestSegment;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestSubsegment;
+import org.apache.camel.component.aws.xray.TestDataBuilder.TestTrace;
+
+public final class TestUtils {
+
+ public static void checkData(Map<String, TestTrace> receivedData, List<TestTrace> testData) {
+ assertThat("Incorrect number of traces",
+ receivedData.size(), is(equalTo(testData.size())));
+ int i = 0;
+ for (String key : receivedData.keySet()) {
+ TestTrace trace = receivedData.get(key);
+ verifyTraces(testData.get(i++), trace);
+ }
+ }
+
+ private static void verifyTraces(TestTrace expected, TestTrace actual) {
+ assertThat("Incorrect number of segment for trace",
+ actual.getSegments().size(), is(equalTo(expected.getSegments().size())));
+ List<TestSegment> expectedSegments = new ArrayList<>(expected.getSegments());
+ List<TestSegment> actualSegments = new ArrayList<>(actual.getSegments());
+
+ boolean randomOrder = expected.isRandomOrder();
+ for (int i = 0; i < expected.getSegments().size(); i++) {
+
+ if (randomOrder) {
+ for (TestSegment expectedSeg : expectedSegments) {
+ boolean found = false;
+ for (TestSegment actualSeg : actualSegments) {
+ if (expectedSeg.getName().equals(actualSeg.getName())) {
+ found = true;
+ verifySegments(expectedSeg, actualSeg);
+ break;
+ }
+ }
+ if (!found) {
+ fail("Could not find expected segment " + expectedSeg.getName());
+ }
+ }
+ } else {
+ verifySegments(expectedSegments.get(i), actualSegments.get(i));
+ }
+ }
+ }
+
+ private static void verifySegments(TestSegment expected, TestSegment actual) {
+ assertThat("Incorrect name of segment",
+ actual.getName(), is(equalTo(expected.getName())));
+
+ boolean randomOrder = expected.isRandomOrder();
+ if (!expected.getSubsegments().isEmpty()) {
+ if (randomOrder) {
+ checkSubsegmentInRandomOrder(expected.getSubsegments(), actual.getSubsegments());
+ } else {
+ for (int i = 0; i < expected.getSubsegments().size(); i++) {
+ if (actual.getName().equals(expected.getName())) {
+ verifySubsegments(expected.getSubsegments().get(i), actual.getSubsegments().get(i));
+ }
+ }
+ }
+ }
+ if (!expected.getAnnotations().isEmpty()) {
+ verifyAnnotations(expected.getAnnotations(), actual.getAnnotations());
+ }
+ if (!expected.getMetadata().isEmpty()) {
+ verifyMetadata(expected.getMetadata(), actual.getMetadata());
+ }
+ }
+
+ private static void verifySubsegments(TestSubsegment expected, TestSubsegment actual) {
+ assertThat("Incorrect name of subsegment",
+ actual.getName(), is(equalTo(expected.getName())));
+
+ boolean randomOrder = expected.isRandomOrder();
+ if (!expected.getSubsegments().isEmpty()) {
+ if (randomOrder) {
+ checkSubsegmentInRandomOrder(expected.getSubsegments(), actual.getSubsegments());
+ } else {
+ for (int i = 0; i < expected.getSubsegments().size(); i++) {
+ verifySubsegments(expected.getSubsegments().get(i), actual.getSubsegments().get(i));
+ }
+ }
+ }
+ if (!expected.getAnnotations().isEmpty()) {
+ verifyAnnotations(expected.getAnnotations(), actual.getAnnotations());
+ }
+ if (!expected.getMetadata().isEmpty()) {
+ verifyMetadata(expected.getMetadata(), actual.getMetadata());
+ }
+ }
+
+ private static void checkSubsegmentInRandomOrder(List<TestSubsegment> expectedSubs, List<TestSubsegment> actualSubs) {
+ for (TestSubsegment expectedSub : expectedSubs) {
+ boolean found = false;
+ for (TestSubsegment actualSub : actualSubs) {
+ if (expectedSub.getName().equals(actualSub.getName())) {
+ found = true;
+ verifySubsegments(expectedSub, actualSub);
+ break;
+ }
+ }
+ if (!found) {
+ fail("Could not find expected sub-segment " + expectedSub.getName());
+ }
+ }
+ }
+
+ private static void verifyAnnotations(Map<String, Object> expected, Map<String, Object> actual) {
+ assertThat(actual.size(), is(equalTo(expected.size())));
+ for (String key : expected.keySet()) {
+ assertTrue("Annotation " + key + " is missing", actual.containsKey(key));
+ assertThat("Annotation value of " + key + " is different",
+ actual.get(key), is(equalTo(expected.get(key))));
+ }
+ }
+
+ private static void verifyMetadata(Map<String, Map<String, Object>> expected,
+ Map<String, Map<String, Object>> actual) {
+
+ assertThat("Insufficient number of metadata found",
+ actual.size(), is(greaterThanOrEqualTo(expected.size())));
+ for (String namespace : expected.keySet()) {
+ assertTrue("Namespace " + namespace + " not found in metadata",
+ actual.containsKey(namespace));
+ for (String key : expected.get(namespace).keySet()) {
+ assertTrue("Key " + key + " of namespace + " + namespace + " not found",
+ actual.get(namespace).containsKey(key));
+ assertThat("Incorrect value of key " + key + " in namespace " + namespace,
+ actual.get(namespace).get(key), is(equalTo(expected.get(namespace).get(key))));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoService2Test.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoService2Test.java
new file mode 100644
index 0000000..19575bc
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoService2Test.java
@@ -0,0 +1,60 @@
+/**
+ * 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.aws.xray;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class TwoService2Test extends CamelAwsXRayTestSupport {
+
+ public TwoService2Test() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("route1")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_direct_ServiceB")
+ .withSubsegment(TestDataBuilder.createSubsegment("route2"))
+ )
+ )
+ );
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:ServiceA", "Hello");
+
+ Thread.sleep(500);
+ verify();
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:ServiceA")
+ .log("ServiceA has been called")
+ .delay(simple("${random(1000,2000)}"))
+ .to("direct:ServiceB");
+
+ from("direct:ServiceB")
+ .log("ServiceB has been called")
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoServiceTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoServiceTest.java
new file mode 100644
index 0000000..2efa720
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoServiceTest.java
@@ -0,0 +1,60 @@
+/**
+ * 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.aws.xray;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class TwoServiceTest extends CamelAwsXRayTestSupport {
+
+ public TwoServiceTest() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("ServiceA")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_direct_ServiceB")
+ .withSubsegment(TestDataBuilder.createSubsegment("ServiceB"))
+ )
+ )
+ );
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:ServiceA", "Hello");
+
+ Thread.sleep(500);
+ verify();
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:ServiceA").routeId("ServiceA")
+ .log("ServiceA has been called")
+ .delay(simple("${random(1000,2000)}"))
+ .to("direct:ServiceB");
+
+ from("direct:ServiceB").routeId("ServiceB")
+ .log("ServiceB has been called")
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoServiceWithExcludeTest.java b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoServiceWithExcludeTest.java
new file mode 100644
index 0000000..84f049c
--- /dev/null
+++ b/components/camel-aws-xray/src/test/java/org/apache/camel/component/aws/xray/TwoServiceWithExcludeTest.java
@@ -0,0 +1,65 @@
+/**
+ * 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.aws.xray;
+
+import java.util.Collections;
+import java.util.Set;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.Test;
+
+public class TwoServiceWithExcludeTest extends CamelAwsXRayTestSupport {
+
+ public TwoServiceWithExcludeTest() {
+ super(
+ TestDataBuilder.createTrace().inRandomOrder()
+ .withSegment(TestDataBuilder.createSegment("ServiceA")
+ .withSubsegment(TestDataBuilder.createSubsegment("SendingTo_direct_ServiceB"))
+ )
+ );
+ }
+
+ @Override
+ protected Set<String> getExcludePatterns() {
+ return Collections.singleton("ServiceB");
+ }
+
+ @Test
+ public void testRoute() throws Exception {
+ template.requestBody("direct:ServiceA", "Hello");
+
+ Thread.sleep(500);
+ verify();
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:ServiceA").routeId("ServiceA")
+ .log("ServiceA has been called")
+ .delay(simple("${random(1000,2000)}"))
+ .to("direct:ServiceB");
+
+ from("direct:ServiceB").routeId("ServiceB")
+ .log("ServiceB has been called")
+ .delay(simple("${random(0,500)}"));
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/resources/logback-test.xml b/components/camel-aws-xray/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..ad0fd41
--- /dev/null
+++ b/components/camel-aws-xray/src/test/resources/logback-test.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<configuration debug="true">
+
+ <appender class="ch.qos.logback.core.ConsoleAppender" name="RootConsoleAppender">
+ <encoder>
+ <pattern>[%-5level] [%40thread] [%-30.30logger{5}] - %msg %n</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="RollingFileAppender"
+ class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>logs/test.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+ <!-- daily rollover -->
+ <fileNamePattern>test.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
+ <maxHistory>5</maxHistory>
+ </rollingPolicy>
+ <encoder>
+ <pattern>[%-5level] %-14.14X{camel.breadcrumbId} - %12.-12X{camel.routeId} - %msg [%thread] [%logger{5}] %n</pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.apache.camel" level="info"/>
+ <logger name="org.apache.camel.component.aws.xray" level="trace"/>
+ <logger name="com.amazonaws.xray" level="trace"/>
+
+ <root>
+ <level value="info"/>
+ <appender-ref ref="RootConsoleAppender"/>
+ </root>
+</configuration>
\ No newline at end of file
diff --git a/components/camel-aws-xray/src/test/resources/org.apache.camel.aws.xray/AwsXRaySimpleRouteTest.xml b/components/camel-aws-xray/src/test/resources/org.apache.camel.aws.xray/AwsXRaySimpleRouteTest.xml
new file mode 100644
index 0000000..016d3c0
--- /dev/null
+++ b/components/camel-aws-xray/src/test/resources/org.apache.camel.aws.xray/AwsXRaySimpleRouteTest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+ ">
+
+ <bean id="aws-xray-tracer" class="org.apache.camel.component.aws.xray.XRayTracer" />
+
+ <camelContext xmlns="http://camel.apache.org/schema/spring">
+ <route id="dude">
+ <from uri="seda:dude"/>
+ <log message="Routing at ${routeId}"/>
+ <delay>
+ <simple>${random(1000,2000)}</simple>
+ </delay>
+ <to uri="direct:car"/>
+ </route>
+ <route id="car">
+ <from uri="direct:car"/>
+ <log message="Routing at ${routeId}"/>
+ <delay>
+ <simple>${random(1000,2000)}</simple>
+ </delay>
+ </route>
+ </camelContext>
+
+</beans>
diff --git a/components/pom.xml b/components/pom.xml
index 00f4967..7b0df17 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -315,6 +315,7 @@
<module>camel-zipkin</module>
<module>camel-zookeeper</module>
<module>camel-zookeeper-master</module>
+ <module>camel-aws-xray</module>
</modules>
--
To stop receiving notification emails like this one, please contact
"commits@camel.apache.org" <co...@camel.apache.org>.