Initial Contribution

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/incubator/open-tracing/pom.xml b/incubator/open-tracing/pom.xml
new file mode 100644
index 0000000..f96771e
--- /dev/null
+++ b/incubator/open-tracing/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.glassfish.jersey.incubator</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.glassfish.jersey.incubator</groupId>
+    <artifactId>jersey-open-tracing</artifactId>
+    <packaging>jar</packaging>
+
+    <name>jersey-open-tracing</name>
+
+    <description>
+        Jersey support for OpenTracing.
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.opentracing</groupId>
+            <artifactId>opentracing-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.opentracing</groupId>
+            <artifactId>opentracing-util</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.core</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>com.sun.istack</groupId>
+                <artifactId>maven-istack-commons-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <inherited>true</inherited>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
new file mode 100644
index 0000000..d155760
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingApplicationEventListener.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseFilter;
+
+import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ContainerResponse;
+import org.glassfish.jersey.server.monitoring.ApplicationEvent;
+import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
+import org.glassfish.jersey.server.monitoring.RequestEvent;
+import org.glassfish.jersey.server.monitoring.RequestEventListener;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapExtractAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Application event listener responsible for creating and propagating server-side request {@link io.opentracing.Span}.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingApplicationEventListener implements ApplicationEventListener {
+    private final Tracer globalTracer = GlobalTracer.get();
+    private final OpenTracingFeature.Verbosity verbosity;
+
+    /**
+     * Creates event listener instance with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}.
+     *
+     * @param verbosity desired verbosity level
+     */
+    public OpenTracingApplicationEventListener(OpenTracingFeature.Verbosity verbosity) {
+        this.verbosity = verbosity;
+    }
+
+    @Override
+    public void onEvent(ApplicationEvent event) {
+        // we don't care about the server lifecycle
+    }
+
+    @Override
+    public RequestEventListener onRequest(RequestEvent requestEvent) {
+        if (requestEvent.getType() == RequestEvent.Type.START) {
+            Span requestSpan = handleRequestStart(requestEvent.getContainerRequest());
+            return new OpenTracingRequestEventListener(requestSpan);
+        }
+        return null;
+    }
+
+    private Span handleRequestStart(ContainerRequest request) {
+
+        final Map<String, String> mappedHeaders = request
+                .getHeaders()
+                .entrySet()
+                .stream()
+                .collect(Collectors.toMap(
+                        Map.Entry::getKey,
+                        (entry) -> OpenTracingUtils.formatList(entry.getValue())));
+
+        final SpanContext extractedContext =
+                globalTracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapExtractAdapter(mappedHeaders));
+
+        Tracer.SpanBuilder spanBuilder = globalTracer
+                .buildSpan(OpenTracingFeature.DEFAULT_REQUEST_SPAN_NAME)
+                .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
+                .withTag(Tags.HTTP_METHOD.getKey(), request.getMethod())
+                .withTag(Tags.HTTP_URL.getKey(), request.getRequestUri().toASCIIString())
+                .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+                        OpenTracingUtils.headersAsString(request.getHeaders()))
+                .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), request.hasEntity());
+
+        if (extractedContext != null) {
+            spanBuilder = spanBuilder.asChildOf(extractedContext);
+        }
+
+        final Span span = spanBuilder.startManual();
+        request.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+        span.log(LocalizationMessages.OPENTRACING_LOG_REQUEST_STARTED());
+        return span;
+    }
+
+    class OpenTracingRequestEventListener implements RequestEventListener {
+        private Span requestSpan;
+        private Span resourceSpan = null;
+
+        OpenTracingRequestEventListener(final Span requestSpan) {
+            this.requestSpan = requestSpan;
+        }
+
+        @Override
+        public void onEvent(RequestEvent event) {
+
+            switch (event.getType()) {
+                case MATCHING_START:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_MATCHING_STARTED());
+                    break;
+
+                case LOCATOR_MATCHED:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_LOCATOR_MATCHED(
+                            OpenTracingUtils.formatList(event.getUriInfo().getMatchedResourceLocators())));
+                    break;
+
+                case SUBRESOURCE_LOCATED:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_SUBRESOURCE_LOCATED(
+                            OpenTracingUtils.formatList(event.getUriInfo().getLocatorSubResources())));
+                    break;
+
+
+                case REQUEST_MATCHED:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_MATCHED(event.getUriInfo()
+                            .getMatchedResourceMethod()
+                            .getInvocable()
+                            .getDefinitionMethod()));
+                    log(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_STARTED());
+                    break;
+
+                case REQUEST_FILTERED:
+                    List<ContainerRequestFilter> requestFilters = new ArrayList<>();
+                    event.getContainerRequestFilters().forEach(requestFilters::add);
+
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_REQUEST_FILTERING_FINISHED(requestFilters.size()));
+                    if (requestFilters.size() > 0) {
+                        log(LocalizationMessages.OPENTRACING_LOG_APPLIED_REQUEST_FILTERS(
+                                OpenTracingUtils.formatProviders(requestFilters)));
+                    }
+                    break;
+
+                case RESOURCE_METHOD_START:
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_STARTED(
+                            event.getUriInfo().getMatchedResourceMethod().getInvocable().getDefinitionMethod()));
+
+                    resourceSpan = globalTracer.buildSpan(OpenTracingFeature.DEFAULT_RESOURCE_SPAN_NAME)
+                                               .asChildOf(requestSpan)
+                                               .startManual();
+
+                    event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, resourceSpan);
+                    break;
+
+                case RESOURCE_METHOD_FINISHED:
+                    log(LocalizationMessages.OPENTRACING_LOG_RESOURCE_METHOD_FINISHED());
+                    break;
+
+                case RESP_FILTERS_START:
+                    // this is the first event after resource method is guaranteed to have finished, even for asynchronous
+                    // processing; resourceSpan will be finished and the span in the context will be switched back to the
+                    // resource span before any further tracing can occur.
+                    event.getContainerRequest().setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, requestSpan);
+                    resourceSpan.finish();
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_STARTED());
+                    break;
+
+                case RESP_FILTERS_FINISHED:
+                    List<ContainerResponseFilter> responseFilters = new ArrayList<>();
+                    event.getContainerResponseFilters().forEach(responseFilters::add);
+                    logVerbose(LocalizationMessages.OPENTRACING_LOG_RESPONSE_FILTERING_FINISHED(responseFilters.size()));
+                    if (responseFilters.size() > 0) {
+                        log(LocalizationMessages.OPENTRACING_LOG_APPLIED_RESPONSE_FILTERS(
+                                OpenTracingUtils.formatProviders(responseFilters)));
+                    }
+                    break;
+
+                case ON_EXCEPTION:
+                    if (resourceSpan != null) {
+                        resourceSpan.setTag(Tags.ERROR.getKey(), true);
+                        resourceSpan.finish();
+                    }
+                    requestSpan.setTag(Tags.ERROR.getKey(), true);
+                    logError(event.getException());
+                    break;
+
+                case EXCEPTION_MAPPER_FOUND:
+                    log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPER_FOUND(
+                            event.getExceptionMapper().getClass().getName()));
+                    break;
+
+                case EXCEPTION_MAPPING_FINISHED:
+                    log(LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_FINISHED()
+                            + (event.isResponseSuccessfullyMapped()
+                            ? LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_SUCCESS()
+                            : LocalizationMessages.OPENTRACING_LOG_EXCEPTION_MAPPING_NOEXCEPTION_OR_FAILED()));
+
+                    break;
+
+
+                case FINISHED:
+                    if (requestSpan != null) {
+                        ContainerResponse response = event.getContainerResponse();
+                        if (response != null) {
+                            int status = response.getStatus();
+                            requestSpan
+                                    .setTag(Tags.HTTP_STATUS.getKey(), status)
+                                    .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), response.hasEntity())
+                                    .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), response.getLength());
+
+                            if (400 <= status) {
+                                requestSpan.setTag(Tags.ERROR.getKey(), true);
+                            }
+                        }
+                        requestSpan.finish();
+                    }
+                    break;
+            }
+        }
+
+        /**
+         * Adds a {@link OpenTracingFeature.Verbosity#TRACE}-level log entry into the request span.
+         * @param s log message
+         */
+        private void logVerbose(String s) {
+            log(OpenTracingFeature.Verbosity.TRACE, s);
+        }
+
+        /**
+         * Adds a {@link OpenTracingFeature.Verbosity#INFO}-level log entry into the request span.
+         * @param s log message
+         */
+        private void log(String s) {
+            log(OpenTracingFeature.Verbosity.INFO, s);
+        }
+
+        /**
+         * Adds a log entry with given {@link org.glassfish.jersey.opentracing.OpenTracingFeature.Verbosity}-level into the
+         * request span.
+         *
+         * @param level desired verbosity level
+         * @param s log message
+         */
+        private void log(OpenTracingFeature.Verbosity level, String s) {
+            if (level.ordinal() <= verbosity.ordinal()) {
+                requestSpan.log(s);
+            }
+        }
+
+        /**
+         * Adds an error log into the request span.
+         * @param t exception to be logged.
+         */
+        private void logError(final Throwable t) {
+            Map<String, Object> errorMap = new HashMap<>(2);
+            errorMap.put("event", "error");
+            errorMap.put("error.object", t);
+            requestSpan.log(errorMap);
+        }
+    }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
new file mode 100644
index 0000000..7934488
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientRequestFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.MediaType;
+
+import io.opentracing.Span;
+import io.opentracing.SpanContext;
+import io.opentracing.Tracer;
+import io.opentracing.propagation.Format;
+import io.opentracing.propagation.TextMapInjectAdapter;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Client-side request filter, that creates the client request {@code Span}.
+ * <p>
+ * Stores request-related metadata into the {@code Span} as {@code Tags}
+ * and {@link GlobalTracer#inject(SpanContext, Format, Object) injects} it into http headers.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+class OpenTracingClientRequestFilter implements ClientRequestFilter {
+
+    @Override
+    public void filter(ClientRequestContext requestContext) throws IOException {
+        Tracer.SpanBuilder spanBuilder = GlobalTracer.get()
+                .buildSpan(LocalizationMessages.OPENTRACING_SPAN_PREFIX_CLIENT() + requestContext.getMethod())
+                .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+                .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
+                .withTag(Tags.HTTP_URL.getKey(), requestContext.getUri().toASCIIString())
+                .withTag(Tags.HTTP_METHOD.getKey(), requestContext.getMethod())
+                .withTag(LocalizationMessages.OPENTRACING_TAG_HAS_REQUEST_ENTITY(), requestContext.hasEntity())
+                .withTag(LocalizationMessages.OPENTRACING_TAG_ACCEPTABLE_MEDIA_TYPES(), requestContext.getAcceptableMediaTypes()
+                        .stream()
+                        .map(MediaType::toString)
+                        .collect(Collectors.joining(", ")))
+                .withTag(LocalizationMessages.OPENTRACING_TAG_REQUEST_HEADERS(),
+                        OpenTracingUtils.headersAsString(requestContext.getHeaders()));
+
+        // if pre-stored "span" property is found, propagate the stored context
+        final Object property = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+        if (property != null && property instanceof SpanContext) {
+            spanBuilder = spanBuilder.asChildOf((SpanContext) property);
+        }
+        Span span = spanBuilder.startManual();
+
+        requestContext.setProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY, span);
+        Map<String, String> addedHeaders = new HashMap<>();
+        GlobalTracer.get().inject(span.context(), Format.Builtin.HTTP_HEADERS, new TextMapInjectAdapter(addedHeaders));
+        addedHeaders.forEach((key, value) -> requestContext.getHeaders().add(key, value));
+    }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
new file mode 100644
index 0000000..0dae48f
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingClientResponseFilter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.io.IOException;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
+
+import io.opentracing.Span;
+import io.opentracing.tag.Tags;
+
+/**
+ * Retrieves stored span from the {@link ClientRequestContext} and adds response details to the tracing info.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingClientResponseFilter implements ClientResponseFilter {
+
+    @Override
+    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
+        final Object spanProperty = requestContext.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+        if (spanProperty != null && spanProperty instanceof Span) {
+            ((Span) spanProperty)
+                    .setTag(Tags.HTTP_STATUS.getKey(), responseContext.getStatus())
+                    .setTag(LocalizationMessages.OPENTRACING_TAG_HAS_RESPONSE_ENTITY(), responseContext.hasEntity())
+                    .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_LENGTH(), responseContext.getLength())
+                    .setTag(LocalizationMessages.OPENTRACING_TAG_RESPONSE_HEADERS(),
+                            OpenTracingUtils.headersAsString(responseContext.getHeaders()))
+                    .finish();
+        }
+    }
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
new file mode 100644
index 0000000..6afb1f9
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingFeature.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.logging.Logger;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+
+import org.glassfish.jersey.Beta;
+
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * A feature that enables OpenTracing support on server and client.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+@Beta
+public class OpenTracingFeature implements Feature {
+    private static final Logger LOGGER = Logger.getLogger(OpenTracingFeature.class.getName());
+    private final Verbosity verbosity;
+
+    /**
+     * Creates feature instance with default ({@link Verbosity#INFO} verbosity level.
+     */
+    public OpenTracingFeature() {
+        verbosity = Verbosity.INFO;
+    }
+
+    /**
+     * Creates feature instance with given ({@link Verbosity} level.
+     * @param verbosity desired level of logging verbosity
+     */
+    public OpenTracingFeature(Verbosity verbosity) {
+        this.verbosity = verbosity;
+    }
+
+    /**
+     * Stored span's {@link ContainerRequestContext} property key.
+     */
+    public static final String SPAN_CONTEXT_PROPERTY = "span";
+
+    /**
+     * Default resource span name.
+     */
+    public static final String DEFAULT_RESOURCE_SPAN_NAME = "jersey-resource";
+
+    /**
+     * Default child span name.
+     */
+    public static final String DEFAULT_CHILD_SPAN_NAME = "jersey-resource-app";
+
+    /**
+     * Default request "root" span name.
+     */
+    public static final String DEFAULT_REQUEST_SPAN_NAME = "jersey-server";
+
+    @Override
+    public boolean configure(FeatureContext context) {
+        if (!GlobalTracer.isRegistered()) {
+            LOGGER.warning(LocalizationMessages.OPENTRACING_TRACER_NOT_REGISTERED());
+        }
+
+        switch (context.getConfiguration().getRuntimeType()) {
+            case CLIENT:
+                context.register(OpenTracingClientRequestFilter.class).register(OpenTracingClientResponseFilter.class);
+                break;
+            case SERVER:
+                context.register(new OpenTracingApplicationEventListener(verbosity));
+        }
+        return true;
+    }
+
+    /**
+     * OpenTracing Jersey event logging verbosity.
+     */
+    public enum Verbosity {
+        /**
+         * Only logs basic Jersey processing related events.
+         */
+        INFO,
+
+        /**
+         * Logs more fine grained events related to Jersey processing.
+         */
+        TRACE
+    }
+
+}
diff --git a/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
new file mode 100644
index 0000000..4bc3ad0
--- /dev/null
+++ b/incubator/open-tracing/src/main/java/org/glassfish/jersey/opentracing/OpenTracingUtils.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.opentracing;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.MultivaluedMap;
+
+import io.opentracing.Span;
+import io.opentracing.Tracer;
+import io.opentracing.util.GlobalTracer;
+
+/**
+ * Utility methods for Jersey OpenTracing integration.
+ *
+ * @author Adam Lindenthal (adam.lindenthal at oracle.com)
+ * @since 2.26
+ */
+public class OpenTracingUtils {
+
+    private OpenTracingUtils() {
+    }
+
+    /**
+     * Resolve resource-level span.
+     * <p>
+     * If open tracing is enabled and {@link GlobalTracer} is registered, resource-level span should be stored in the
+     * {@link OpenTracingFeature#SPAN_CONTEXT_PROPERTY}. This span is resolved and returned as an {@link Optional}.
+     *
+     * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+     * @return {@link Optional} of the resolved span, if found; empty optional if not
+     */
+    public static Optional<Span> getRequestSpan(final ContainerRequestContext context) {
+        if (context != null) {
+            final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+            if (spanProperty != null && spanProperty instanceof Span) {
+                return Optional.of((Span) spanProperty);
+            }
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Create and start ad-hoc custom span with the default name as a child span of the request span (if available).
+     *
+     * @param context {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+     * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+     * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+     * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+     * after the traced code finishes.
+     */
+    public static Span getRequestChildSpan(final ContainerRequestContext context) {
+        return getRequestChildSpan(context, OpenTracingFeature.DEFAULT_CHILD_SPAN_NAME);
+    }
+
+    /**
+     * Create and start ad-hoc custom span with a custom name as a child span of the request span (if available).
+     *
+     * @param context  {@link ContainerRequestContext} instance, can be obtained via {@code @Context} injection
+     * @param spanName name to be used for the created span
+     * @return If parent span ("request span") instance is stored in the {@code ContainerRequestContext}, new span is created
+     * as a child span of the found span. If no parent span found, new "root" span is created. In both cases, the returned span
+     * is already started. In order to successfully store the tracing, {@link Span#finish()} needs to be invoked explicitly,
+     * after the traced code finishes.
+     */
+    public static Span getRequestChildSpan(final ContainerRequestContext context, final String spanName) {
+        Tracer.SpanBuilder spanBuilder = GlobalTracer.get().buildSpan(spanName);
+        if (context != null) {
+            final Object spanProperty = context.getProperty(OpenTracingFeature.SPAN_CONTEXT_PROPERTY);
+            if (spanProperty != null && spanProperty instanceof Span) {
+                spanBuilder = spanBuilder.asChildOf((Span) spanProperty);
+            }
+        }
+        return spanBuilder.startManual();
+    }
+
+    /**
+     * Convert request/response headers from {@link MultivaluedMap} into printable form.
+     *
+     * @param headers multi-valued map of request or response headers
+     * @return {@code String} representation, e.g. "[header1=foo]; [header2=bar, baz]"
+     */
+    static String headersAsString(final MultivaluedMap<String, ?> headers) {
+        return headers.entrySet()
+                .stream()
+                .map((entry) -> "["
+                        + entry.getKey() + "="
+                        + entry.getValue()
+                        .stream()
+                        .map(Object::toString)
+                        .collect(Collectors.joining(", "))
+                        + "]")
+                .collect(Collectors.joining("; "));
+    }
+
+    static String formatList(List<?> list) {
+        return list.stream().map(Object::toString).collect(Collectors.joining(", "));
+    }
+
+    static String formatProviders(Iterable<?> providers) {
+        return StreamSupport.stream(providers.spliterator(), false)
+                .map((provider) -> provider.getClass().getName())
+                .collect(Collectors.joining(", "));
+    }
+}
diff --git a/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
new file mode 100644
index 0000000..67f8508
--- /dev/null
+++ b/incubator/open-tracing/src/main/resources/org/glassfish/jersey/opentracing/localization.properties
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+opentracing.tracer.not.registered=OpenTracing feature is enabled, but no Tracer has been registered. Default no-op tracer will \
+  be used.
+opentracing.log.request.started=Request started.
+opentracing.log.matching.started=Resource matching started.
+opentracing.log.request.matched=Request matched, method: {0}
+opentracing.log.locator.matched=Locator matched. Matched locators: {0}
+opentracing.log.subresource.located=Subresource located: {0}
+opentracing.log.request.filtering.started=Request filtering started.
+opentracing.log.request.filtering.finished=Request filtering finished, {0} filter(s) applied.
+opentracing.log.applied.request.filters=Applied request filters: {0}.
+opentracing.log.resource.method.started=Resource method {0} started.
+opentracing.log.response.filtering.started=Response filtering started.
+opentracing.log.resource.method.finished=Resource method finished.
+opentracing.log.response.filtering.finished=Response filtering finished, {0} filter(s) applied.
+opentracing.log.applied.response.filters=Applied reqsponse filters: {0}.
+opentracing.log.exception=Exception while processing the request: {0}.
+opentracing.log.exception.mapper.found=Exception mapper found: {0}.
+opentracing.log.exception.mapping.finished=Exception mapping finished: 
+opentracing.log.exception.mapping.success=successfully mapped
+opentracing.log.exception.mapping.noexception.or.failed=no exception or mapping failed.
+opentracing.tag.request.headers=requestHeaders
+opentracing.tag.response.headers=responseHeaders
+opentracing.tag.has.request.entity=hasRequestEntity
+opentracing.tag.has.response.entity=hasResponseEntity
+opentracing.tag.response.length=responseLength
+opentracing.tag.acceptable.media.types=acceptableMediaTypes
+opentracing.span.prefix.client=jersey-client-
+