From dfe08a62ea5fefcbea60fbf0573fc8fdb18e8975 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 3 Jul 2019 16:44:50 +0200 Subject: [PATCH 1/9] MP Opentracing Implementation Signed-off-by: Tomas Langer --- bom/pom.xml | 7 + .../docs/microprofile/09_rest-client.adoc | 108 +++++++++++++ .../bundles/helidon-microprofile-2.2/pom.xml | 11 +- .../src/main/java9/module-info.java | 3 + microprofile/pom.xml | 1 + microprofile/rest-client/pom.xml | 82 ++++++++++ .../src/main/java9/module-info.java | 23 +++ microprofile/tests/arquillian/pom.xml | 8 +- .../microprofile/arquillian/ServerRunner.java | 13 +- microprofile/tests/tck/pom.xml | 1 + .../tests/tck/tck-fault-tolerance/pom.xml | 24 --- microprofile/tests/tck/tck-health/pom.xml | 12 -- microprofile/tests/tck/tck-jwt-auth/pom.xml | 13 -- ...boss.arquillian.core.spi.LoadableExtension | 16 -- microprofile/tests/tck/tck-metrics/pom.xml | 22 +-- microprofile/tests/tck/tck-openapi/pom.xml | 12 -- .../tests/tck/tck-opentracing/pom.xml | 97 ++++++++++++ .../opentracing/tck/OpentracingExtension.java | 31 ++++ .../tck/OpentracingJavaMockTracerBuilder.java | 106 +++++++++++++ .../OpentracingJavaMockTracerProvider.java | 14 ++ .../opentracing/tck/UrlResourceProvider.java | 44 ++++++ .../src/test/resources/META-INF/beans.xml | 26 ++++ .../io.helidon.tracing.spi.TracerProvider | 17 ++ ...boss.arquillian.core.spi.LoadableExtension | 18 +++ .../src/test/resources/arquillian.xml | 35 +++++ .../src/test/resources/tck-application.yaml | 7 + .../tck/tck-opentracing/tck-suite.xml} | 18 ++- microprofile/tracing/pom.xml | 16 +- .../tracing/MpTracingCdiExtension.java | 21 +++ .../tracing/MpTracingClientRegistrar.java | 53 +++++++ .../tracing/MpTracingContextFilter.java | 9 +- .../microprofile/tracing/MpTracingFilter.java | 147 ++++++++++++++++-- .../tracing/MpTracingInterceptor.java | 117 ++++++++++++++ .../tracing/MpTracingRestClientFilter.java | 67 ++++++++ .../tracing/MpTracingRestClientListener.java | 44 ++++++ .../microprofile/tracing/TracerProducer.java | 28 ++++ .../tracing/src/main/java9/module-info.java | 8 +- .../javax.enterprise.inject.spi.Extension | 17 ++ ...opentracing.ClientTracingRegistrarProvider | 17 ++ ...profile.rest.client.spi.RestClientListener | 17 ++ ...sfish.jersey.internal.spi.AutoDiscoverable | 2 +- .../META-INF/microprofile-config.properties | 17 ++ pom.xml | 22 +++ .../jersey/client/ClientTracingFilter.java | 63 +++++++- .../tracing/jersey/AbstractTracingFilter.java | 30 +++- .../helidon/tracing/jersey/TracingHelper.java | 31 +++- 46 files changed, 1348 insertions(+), 147 deletions(-) create mode 100644 docs/src/main/docs/microprofile/09_rest-client.adoc create mode 100644 microprofile/rest-client/pom.xml create mode 100644 microprofile/rest-client/src/main/java9/module-info.java create mode 100644 microprofile/tests/tck/tck-opentracing/pom.xml create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingExtension.java create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerBuilder.java create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/beans.xml create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/io.helidon.tracing.spi.TracerProvider create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/resources/arquillian.xml create mode 100644 microprofile/tests/tck/tck-opentracing/src/test/resources/tck-application.yaml rename microprofile/{tracing/src/main/resources/META-INF/beans.xml => tests/tck/tck-opentracing/tck-suite.xml} (58%) create mode 100644 microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java create mode 100644 microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java create mode 100644 microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingInterceptor.java create mode 100644 microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java create mode 100644 microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java create mode 100644 microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java create mode 100644 microprofile/tracing/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension create mode 100644 microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider create mode 100644 microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener create mode 100644 microprofile/tracing/src/test/resources/META-INF/microprofile-config.properties diff --git a/bom/pom.xml b/bom/pom.xml index 8a42ed7ff45..11138424f91 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -409,6 +409,13 @@ ${project.version} + + + io.helidon.microprofile.rest-client + helidon-microprofile-rest-client + ${project.version} + + io.helidon.serviceconfiguration diff --git a/docs/src/main/docs/microprofile/09_rest-client.adoc b/docs/src/main/docs/microprofile/09_rest-client.adoc new file mode 100644 index 00000000000..9d22f16c9ba --- /dev/null +++ b/docs/src/main/docs/microprofile/09_rest-client.adoc @@ -0,0 +1,108 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + 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. + +/////////////////////////////////////////////////////////////////////////////// + += Rest Client +:description: Helidon MP Rest Client += :keywords: helidon, rest, client, microprofile, micro-profile + +== Configuring Rest Client with Helidon MP +Declare the following dependency in your project: + +[source,xml] +---- + + io.helidon.microprofile.restclient + helidon-microprofile-rest-client + +---- + +== Creating new client - Interface +MicroProfile Rest Client can be created by using RestClientBuilder class +and calling method newBuilder. Then you need to specify the baseUri you want to +target on and then just call method build. This method accepts only one +parameter and it is an interface you want to create client from. + +Example: +[source,java] +---- +SomeInterface someInterface = + RestClientBuilder.newBuilder() + .baseUri("localhost:8080") + .build(SomeInterface.class); +remoteApi.someMethod(apiModel); +---- + +== Creating new client - Annotation +It is also possible to create rest client via annotations. + + +MicroProfile Rest Client can be created by using RestClientBuilder class +and calling method newBuilder. Then you need to specify the baseUri you want to +target on and then just call method build. This method accepts only one +parameter and it is an interface you want to create client from. + +Example: +[source,java] +---- +SomeInterface someInterface = + RestClientBuilder.newBuilder() + .baseUri("localhost:8080") + .build(SomeInterface.class); +remoteApi.someMethod(apiModel); +---- + +== Trace propagation across services +Automated trace propagation is supported currently only with Jersey client. + +Tracing propagation works automatically as long as you execute +the client call in the same thread as the Jersey server side. + +_Exceptions that require manual handling_: + +- When the resource method is annotated with Fault Tolerance annotations (e.g. `@Fallback`) +- When you use `async()` on the Jersey client request + +In such cases, you must provide the SpanContext by hand: +[source,java] +.Tracing propagation with Jersey client (on a different thread) +---- +import static io.helidon.tracing.jersey.client.ClientTracingFilter.CURRENT_SPAN_CONTEXT_PROPERTY_NAME; +import static io.helidon.tracing.jersey.client.ClientTracingFilter.TRACER_PROPERTY_NAME; + +// ... + +Response response = client.target(serviceEndpoint) + .request() + // tracer should be provided unless available as GlobalTracer + .property(TRACER_PROPERTY_NAME, tracer) + .property(CURRENT_SPAN_CONTEXT_PROPERTY_NAME, spanContext) + .get(); +---- + +Tracer and SpanContext can be obtained from `ServerRequest` that can be injected into Resource classes: + +---- +@Context +io.helidon.webserver.ServerRequest serverRequest; + +//... + +SpanContext spanContext = serverRequest.spanContext(); +// optional, you could also use GlobalTracer.get() if it is configured +Tracer tracer = req.webServer().configuration().tracer(); +---- diff --git a/microprofile/bundles/helidon-microprofile-2.2/pom.xml b/microprofile/bundles/helidon-microprofile-2.2/pom.xml index 807223015dd..f56400b5a92 100644 --- a/microprofile/bundles/helidon-microprofile-2.2/pom.xml +++ b/microprofile/bundles/helidon-microprofile-2.2/pom.xml @@ -39,7 +39,16 @@ io.helidon.microprofile.openapi helidon-microprofile-openapi ${project.version} - runtime + + + io.helidon.microprofile.rest-client + helidon-microprofile-rest-client + ${project.version} + + + io.helidon.microprofile.tracing + helidon-microprofile-tracing + ${project.version} diff --git a/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java b/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java index 20b3c858922..eed5a467264 100644 --- a/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java +++ b/microprofile/bundles/helidon-microprofile-2.2/src/main/java9/module-info.java @@ -25,6 +25,9 @@ requires transitive io.helidon.microprofile.metrics; requires transitive io.helidon.microprofile.faulttolerance; requires transitive io.helidon.microprofile.jwt.auth.cdi; + requires transitive io.helidon.microprofile.tracing; + requires transitive io.helidon.microprofile.restclient; + requires transitive io.helidon.microprofile.openapi; requires io.helidon.health.checks; } diff --git a/microprofile/pom.xml b/microprofile/pom.xml index a73605f8078..a6e637df4f1 100644 --- a/microprofile/pom.xml +++ b/microprofile/pom.xml @@ -44,5 +44,6 @@ jwt-auth tracing openapi + rest-client diff --git a/microprofile/rest-client/pom.xml b/microprofile/rest-client/pom.xml new file mode 100644 index 00000000000..9390a439268 --- /dev/null +++ b/microprofile/rest-client/pom.xml @@ -0,0 +1,82 @@ + + + + 4.0.0 + + io.helidon.microprofile + helidon-microprofile-project + 1.1.3-SNAPSHOT + + + io.helidon.microprofile.rest-client + helidon-microprofile-rest-client + Helidon Microprofile Rest Client + + + + org.glassfish.jersey.ext.microprofile + jersey-mp-rest-client + + + org.glassfish.jersey.core + jersey-server + + + org.glassfish.jersey.core + jersey-client + + + org.glassfish.jersey.inject + jersey-hk2 + + + org.glassfish.jersey.media + jersey-media-json-binding + + + org.glassfish.jersey.ext.cdi + jersey-cdi1x + + + jakarta.json + jakarta.json-api + + + javax.enterprise + cdi-api + + + org.glassfish + jsonp-jaxrs + + + jakarta.ws.rs + jakarta.ws.rs-api + + + org.eclipse.microprofile.config + microprofile-config-api + + + + + + \ No newline at end of file diff --git a/microprofile/rest-client/src/main/java9/module-info.java b/microprofile/rest-client/src/main/java9/module-info.java new file mode 100644 index 00000000000..4fe8d39eecc --- /dev/null +++ b/microprofile/rest-client/src/main/java9/module-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +/** + * MP Rest clienr. + */ +module io.helidon.microprofile.restclient { + requires transitive microprofile.rest.client.api; + requires jersey.mp.rest.client; +} diff --git a/microprofile/tests/arquillian/pom.xml b/microprofile/tests/arquillian/pom.xml index 7e5b4e1bfb2..822141fbbca 100644 --- a/microprofile/tests/arquillian/pom.xml +++ b/microprofile/tests/arquillian/pom.xml @@ -56,8 +56,14 @@ io.helidon.microprofile.bundles - helidon-microprofile-1.1 + helidon-microprofile-2.2 ${project.version} + + + io.helidon.health + helidon-health-checks + + junit diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java index 440205dcad0..9a088843213 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import javax.ws.rs.core.Application; import io.helidon.config.Config; +import io.helidon.microprofile.config.MpConfig; import io.helidon.microprofile.server.Server; import org.glassfish.jersey.server.ResourceConfig; @@ -46,14 +47,18 @@ private static String getContextRoot(Class application) { if (null == path) { return null; } - return path.value(); + String value = path.value(); + return value.startsWith("/") ? value : "/" + value; } void start(Config config, HelidonContainerConfiguration containerConfig, Set classNames, ClassLoader cl) { //cl.getResources("beans.xml") Server.Builder builder = Server.builder() .port(containerConfig.getPort()) - .config(config); + .config(MpConfig.builder() + .config(config) + .addDiscoveredSources() + .build()); handleClasses(cl, classNames, builder, containerConfig.getAddResourcesToApps()); @@ -87,7 +92,7 @@ private void handleClasses(ClassLoader classLoader, if (Application.class.isAssignableFrom(c)) { LOGGER.finest(() -> "Adding application class: " + c.getName()); applicationClasses.add(c); - } else if (c.isAnnotationPresent(Path.class)) { + } else if (c.isAnnotationPresent(Path.class) && !c.isInterface()) { LOGGER.finest(() -> "Adding resource class: " + c.getName()); resourceClasses.add(c); } else { diff --git a/microprofile/tests/tck/pom.xml b/microprofile/tests/tck/pom.xml index 274e4a7960b..4dd87240274 100644 --- a/microprofile/tests/tck/pom.xml +++ b/microprofile/tests/tck/pom.xml @@ -37,6 +37,7 @@ tck-fault-tolerance tck-jwt-auth tck-openapi + tck-opentracing diff --git a/microprofile/tests/tck/tck-fault-tolerance/pom.xml b/microprofile/tests/tck/tck-fault-tolerance/pom.xml index 9783387c80a..2d166a642ff 100644 --- a/microprofile/tests/tck/tck-fault-tolerance/pom.xml +++ b/microprofile/tests/tck/tck-fault-tolerance/pom.xml @@ -30,30 +30,6 @@ Helidon Microprofile Tests TCK Fault Tolerance - - io.helidon.microprofile - helidon-microprofile-fault-tolerance - ${project.version} - test - - - io.helidon.microprofile.metrics - helidon-microprofile-metrics - ${project.version} - test - - - io.helidon.microprofile.config - helidon-microprofile-config - ${project.version} - test - - - io.helidon.microprofile.config - helidon-microprofile-config-cdi - ${project.version} - test - io.helidon.microprofile.tests helidon-arquillian diff --git a/microprofile/tests/tck/tck-health/pom.xml b/microprofile/tests/tck/tck-health/pom.xml index baf342df5b9..2ab361f1006 100644 --- a/microprofile/tests/tck/tck-health/pom.xml +++ b/microprofile/tests/tck/tck-health/pom.xml @@ -31,18 +31,6 @@ Helidon Microprofile Tests TCK Health - - io.helidon.microprofile.bundles - helidon-microprofile-1.1 - ${project.version} - test - - - io.helidon.microprofile.health - helidon-microprofile-health - ${project.version} - test - io.helidon.microprofile.tests helidon-arquillian diff --git a/microprofile/tests/tck/tck-jwt-auth/pom.xml b/microprofile/tests/tck/tck-jwt-auth/pom.xml index 4b3f2b758f2..f6eb4005fe4 100644 --- a/microprofile/tests/tck/tck-jwt-auth/pom.xml +++ b/microprofile/tests/tck/tck-jwt-auth/pom.xml @@ -27,19 +27,6 @@ Helidon Microprofile Tests TCK JWT-Auth - - io.helidon.microprofile.jwt - helidon-microprofile-jwt-auth-cdi - ${project.version} - - - org.glassfish.jersey.core - jersey-client - - - org.glassfish.jersey.inject - jersey-hk2 - org.eclipse.microprofile.jwt microprofile-jwt-auth-tck diff --git a/microprofile/tests/tck/tck-jwt-auth/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/microprofile/tests/tck/tck-jwt-auth/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension index fada19378ec..46df7d65126 100644 --- a/microprofile/tests/tck/tck-jwt-auth/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension +++ b/microprofile/tests/tck/tck-jwt-auth/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -14,21 +14,5 @@ # limitations under the License. # -# -# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# 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. -# - io.helidon.microprofile.jwtauth.tck.JwtAuthExtension diff --git a/microprofile/tests/tck/tck-metrics/pom.xml b/microprofile/tests/tck/tck-metrics/pom.xml index ba77a09dae6..b61f29126b8 100644 --- a/microprofile/tests/tck/tck-metrics/pom.xml +++ b/microprofile/tests/tck/tck-metrics/pom.xml @@ -31,8 +31,8 @@ - io.helidon.microprofile.metrics - helidon-microprofile-metrics + io.helidon.microprofile.tests + helidon-arquillian ${project.version} test @@ -41,24 +41,6 @@ jaxb-api test - - io.helidon.microprofile.config - helidon-microprofile-config - ${project.version} - test - - - io.helidon.microprofile.config - helidon-microprofile-config-cdi - ${project.version} - test - - - io.helidon.microprofile.tests - helidon-arquillian - ${project.version} - test - org.jboss.arquillian.junit arquillian-junit-container diff --git a/microprofile/tests/tck/tck-openapi/pom.xml b/microprofile/tests/tck/tck-openapi/pom.xml index 129595d60e2..f5942358d03 100644 --- a/microprofile/tests/tck/tck-openapi/pom.xml +++ b/microprofile/tests/tck/tck-openapi/pom.xml @@ -31,18 +31,6 @@ Helidon Microprofile Tests TCK OpenAPI - - io.helidon.microprofile.bundles - helidon-microprofile-1.2 - ${project.version} - test - - - io.helidon.microprofile.openapi - helidon-microprofile-openapi - ${project.version} - test - io.helidon.microprofile.tests helidon-arquillian diff --git a/microprofile/tests/tck/tck-opentracing/pom.xml b/microprofile/tests/tck/tck-opentracing/pom.xml new file mode 100644 index 00000000000..df78cb7e5ca --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/pom.xml @@ -0,0 +1,97 @@ + + + + + + tck-project + io.helidon.microprofile.tests + 1.1.3-SNAPSHOT + + 4.0.0 + + tck-opentracing + Helidon Microprofile Tests TCK Opentracing + + + + io.helidon.microprofile.tests + helidon-arquillian + ${project.version} + test + + + + org.glassfish.jersey.media + jersey-media-json-binding + + + + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + javax.xml.bind + jaxb-api + test + + + org.eclipse.microprofile.opentracing + microprofile-opentracing-tck + test + + + org.jboss.resteasy + resteasy-client + + + + + org.eclipse.microprofile.opentracing + microprofile-opentracing-tck-rest-client + test + + + org.jboss.resteasy + resteasy-client + + + + + io.opentracing + opentracing-mock + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + tck-suite.xml + + + + + + \ No newline at end of file diff --git a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingExtension.java b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingExtension.java new file mode 100644 index 00000000000..5cbc373527a --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingExtension.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.opentracing.tck; + +import org.jboss.arquillian.container.test.impl.enricher.resource.URLResourceProvider; +import org.jboss.arquillian.core.spi.LoadableExtension; +import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider; + +/** + * LoadableExtension that will load the HealthResourceProvider. + */ +public class OpentracingExtension implements LoadableExtension { + @Override + public void register(ExtensionBuilder extensionBuilder) { + extensionBuilder.override(ResourceProvider.class, URLResourceProvider.class, UrlResourceProvider.class); + } +} diff --git a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerBuilder.java b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerBuilder.java new file mode 100644 index 00000000000..1a267d0ab02 --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerBuilder.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.opentracing.tck; + +import java.net.URI; + +import io.helidon.config.Config; +import io.helidon.tracing.TracerBuilder; + +import io.opentracing.Tracer; +import io.opentracing.mock.MockTracer; +import io.opentracing.util.GlobalTracer; + +public final class OpentracingJavaMockTracerBuilder implements TracerBuilder { + + private OpentracingJavaMockTracerBuilder() { + } + + public static TracerBuilder create() { + return new OpentracingJavaMockTracerBuilder(); + } + + @Override + public OpentracingJavaMockTracerBuilder serviceName(String name) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder collectorUri(URI uri) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder collectorProtocol(String protocol) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder collectorHost(String host) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder collectorPort(int port) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder collectorPath(String path) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder enabled(boolean enabled) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder registerGlobal(boolean global) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder addTracerTag(String key, String value) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder addTracerTag(String key, Number value) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder addTracerTag(String key, boolean value) { + return this; + } + + @Override + public OpentracingJavaMockTracerBuilder config(Config config) { + return this; + } + + /** + * Builds a mock {@link Tracer}. + * + * @return the tracer + */ + @Override + public Tracer build() { + return new MockTracer(); + } +} diff --git a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java new file mode 100644 index 00000000000..1b57ac4922d --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java @@ -0,0 +1,14 @@ +package io.helidon.microprofile.opentracing.tck; + +import io.helidon.tracing.TracerBuilder; +import io.helidon.tracing.spi.TracerProvider; + +/** + * Created by David Kral. + */ +public class OpentracingJavaMockTracerProvider implements TracerProvider { + @Override + public TracerBuilder createBuilder() { + return OpentracingJavaMockTracerBuilder.create(); + } +} diff --git a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java new file mode 100644 index 00000000000..b407e79cd93 --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.opentracing.tck; + +import java.lang.annotation.Annotation; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; + +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.arquillian.test.spi.enricher.resource.ResourceProvider; + +/** + * TCKs use addition when creating URL for a client. The default Arquillian implementation returns url without the trailing + * /. + */ +public class UrlResourceProvider implements ResourceProvider { + @Override + public Object lookup(ArquillianResource arquillianResource, Annotation... annotations) { + try { + return URI.create("http://localhost:8080/").toURL(); + } catch (MalformedURLException e) { + return null; + } + } + + @Override + public boolean canProvide(Class type) { + return type.isAssignableFrom(URL.class); + } +} diff --git a/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/beans.xml b/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/beans.xml new file mode 100644 index 00000000000..f472ff44ff2 --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/beans.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/io.helidon.tracing.spi.TracerProvider b/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/io.helidon.tracing.spi.TracerProvider new file mode 100644 index 00000000000..6d23b710970 --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/io.helidon.tracing.spi.TracerProvider @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +io.helidon.microprofile.opentracing.tck.OpentracingJavaMockTracerProvider \ No newline at end of file diff --git a/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 00000000000..f025f467e32 --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1,18 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +io.helidon.microprofile.opentracing.tck.OpentracingExtension + diff --git a/microprofile/tests/tck/tck-opentracing/src/test/resources/arquillian.xml b/microprofile/tests/tck/tck-opentracing/src/test/resources/arquillian.xml new file mode 100644 index 00000000000..6c1253f8261 --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/resources/arquillian.xml @@ -0,0 +1,35 @@ + + + + + + target/deployments + 8080 + + + + + true + + + \ No newline at end of file diff --git a/microprofile/tests/tck/tck-opentracing/src/test/resources/tck-application.yaml b/microprofile/tests/tck/tck-opentracing/src/test/resources/tck-application.yaml new file mode 100644 index 00000000000..bf269f0f0ff --- /dev/null +++ b/microprofile/tests/tck/tck-opentracing/src/test/resources/tck-application.yaml @@ -0,0 +1,7 @@ +tracing: + service: "TCK" + components: + web-server: + enabled: false + security: + enabled: false \ No newline at end of file diff --git a/microprofile/tracing/src/main/resources/META-INF/beans.xml b/microprofile/tests/tck/tck-opentracing/tck-suite.xml similarity index 58% rename from microprofile/tracing/src/main/resources/META-INF/beans.xml rename to microprofile/tests/tck/tck-opentracing/tck-suite.xml index 2b44e42f316..5ade73e0934 100644 --- a/microprofile/tracing/src/main/resources/META-INF/beans.xml +++ b/microprofile/tests/tck/tck-opentracing/tck-suite.xml @@ -1,6 +1,6 @@ - + + - - + + + + + + + \ No newline at end of file diff --git a/microprofile/tracing/pom.xml b/microprofile/tracing/pom.xml index ec43b7c8e2d..a5a022e8da2 100644 --- a/microprofile/tracing/pom.xml +++ b/microprofile/tracing/pom.xml @@ -32,10 +32,15 @@ Support for distributed tracing in Helidon MP and - (TODO) implementation of Microprofile Opentracing specification. + implementation of Microprofile Opentracing specification. + + javax.enterprise + cdi-api + provided + io.helidon.tracing helidon-tracing-jersey @@ -46,6 +51,10 @@ helidon-microprofile-server ${project.version} + + org.eclipse.microprofile.opentracing + microprofile-opentracing-api + javax.activation @@ -64,5 +73,10 @@ ${project.version} test + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + compile + diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java new file mode 100644 index 00000000000..62484e766df --- /dev/null +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java @@ -0,0 +1,21 @@ +package io.helidon.microprofile.tracing; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.BeforeBeanDiscovery; +import javax.enterprise.inject.spi.Extension; + +/** + * CDI extension for Microprofile Tracing implementation. + */ +public class MpTracingCdiExtension implements Extension { + /** + * Add our beans to CDI, so we do not need to use {@code beans.xml}. + * + * @param bbd CDI event + */ + public void observeBeforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) { + bbd.addAnnotatedType(MpTracingInterceptor.class, "TracingInterceptor"); + bbd.addAnnotatedType(TracerProducer.class, "TracingTracerProducer"); + } + +} diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java new file mode 100644 index 00000000000..3c02b52983a --- /dev/null +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingClientRegistrar.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.tracing; + +import java.util.concurrent.ExecutorService; + +import javax.ws.rs.client.ClientBuilder; + +import io.helidon.common.configurable.ThreadPoolSupplier; +import io.helidon.common.context.Contexts; +import io.helidon.microprofile.config.MpConfig; +import io.helidon.tracing.jersey.client.ClientTracingFilter; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider; + +/** + * Microprofile client tracing registrar. + */ +public class MpTracingClientRegistrar implements ClientTracingRegistrarProvider { + private static final ClientTracingFilter FILTER = new ClientTracingFilter(); + static final ThreadPoolSupplier EXECUTOR_SERVICE; + + static { + MpConfig config = (MpConfig) ConfigProvider.getConfig(); + EXECUTOR_SERVICE = ThreadPoolSupplier.create(config.helidonConfig().get("tracing.executor-service")); + } + + @Override + public ClientBuilder configure(ClientBuilder clientBuilder) { + return configure(clientBuilder, EXECUTOR_SERVICE.get()); + } + + @Override + public ClientBuilder configure(ClientBuilder clientBuilder, ExecutorService executorService) { + clientBuilder.register(FILTER); + clientBuilder.executorService(Contexts.wrap(executorService)); + return clientBuilder; + } +} diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java index 41f636e9532..75538326f8f 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingContextFilter.java @@ -17,7 +17,6 @@ import javax.annotation.Priority; import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; import javax.inject.Provider; import javax.ws.rs.ConstrainedTo; import javax.ws.rs.RuntimeType; @@ -27,12 +26,13 @@ import javax.ws.rs.core.Context; import io.helidon.common.context.Contexts; -import io.helidon.config.Config; import io.helidon.tracing.jersey.client.internal.TracingContext; import io.helidon.webserver.ServerRequest; import io.opentracing.SpanContext; import io.opentracing.Tracer; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; /** * Automatically registered filter that stores @@ -49,8 +49,7 @@ public class MpTracingContextFilter implements ContainerRequestFilter { @Context private Provider request; - @Inject - private Config config; + private final Config config = ConfigProvider.getConfig(); @Override public void filter(ContainerRequestContext requestContext) { @@ -59,7 +58,7 @@ public void filter(ContainerRequestContext requestContext) { Tracer tracer = serverRequest.tracer(); SpanContext parentSpan = serverRequest.spanContext(); - boolean clientEnabled = config.get("tracing.client.enabled").asBoolean().orElse(true); + boolean clientEnabled = config.getOptionalValue("tracing.client.enabled", Boolean.class).orElse(true); TracingContext tracingContext = TracingContext.create(tracer, serverRequest.headers().toMap(), clientEnabled); tracingContext.parentSpan(parentSpan); diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java index 91674c06690..e997d370c22 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java @@ -15,18 +15,31 @@ */ package io.helidon.microprofile.tracing; +import java.lang.reflect.Method; +import java.net.URI; import java.util.Optional; +import java.util.function.Function; +import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.annotation.Priority; import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.ConstrainedTo; +import javax.ws.rs.Path; import javax.ws.rs.RuntimeType; import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ResourceInfo; +import javax.ws.rs.core.Context; import io.helidon.tracing.jersey.AbstractTracingFilter; import io.opentracing.Tracer; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.opentracing.Traced; +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.model.Invocable; +import org.glassfish.jersey.server.model.ResourceMethod; /** * Adds tracing of Jersey calls using a post-matching filter. @@ -35,8 +48,12 @@ @ConstrainedTo(RuntimeType.SERVER) @Priority(Integer.MIN_VALUE + 5) @ApplicationScoped -public class MpTracingFilter extends AbstractTracingFilter { +public class MpTracingFilter extends AbstractTracingFilter { + @Context + private ResourceInfo resourceInfo; + private MpTracingHelper utils; + private Function skipPatternFunction; /** * Post construct method, initialization procedures. @@ -44,24 +61,44 @@ public class MpTracingFilter extends AbstractTracingFilter { @PostConstruct public void postConstruct() { this.utils = MpTracingHelper.create(); + // use skip pattern first + Config config = ConfigProvider.getConfig(); + + Optional skipPattern = config.getOptionalValue("mp.opentracing.server.skip-pattern", String.class); + + this.skipPatternFunction = skipPattern.map(Pattern::compile) + .map(pattern -> (Function) path -> pattern.matcher(path).matches()) + .orElse(path -> false); } @Override protected boolean tracingEnabled(ContainerRequestContext context) { - // first let us find if we should trace or not - // Optional traced = findTraced(context); - Optional traced = Optional.empty(); - - if (traced.isPresent()) { - // this is handled by CDI extension for annotated resources + if (skipPatternFunction.apply(addForwardSlash(context.getUriInfo().getPath()))) { return false; } - return utils.tracingEnabled(); + return findTraced(context) + .map(Traced::value) + .orElseGet(utils::tracingEnabled); + } + + private String addForwardSlash(String path) { + if (path.isEmpty()) { + return "/"; + } + + if (path.charAt(0) == '/') { + return path; + } + + return "/" + path; } @Override protected String spanName(ContainerRequestContext context) { - return utils.operationName(context); + return findTraced(context) + .map(Traced::operationName) + .filter(str -> !str.isEmpty()) + .orElseGet(() -> utils.operationName(context)); } @Override @@ -69,8 +106,92 @@ protected void configureSpan(Tracer.SpanBuilder spanBuilder) { } -// private Optional findTraced(ContainerRequestContext requestContext) { -// // TODO all annotated by "Traced" must be handled by CDI extension -// return Optional.empty(); -// } + @Override + protected String url(ContainerRequestContext requestContext) { + String hostHeader = requestContext.getHeaderString("host"); + URI requestUri = requestContext.getUriInfo().getRequestUri(); + + if (null != hostHeader) { + String query = requestUri.getQuery(); + if (null == query) { + query = ""; + } else { + if (!query.isEmpty()) { + query = "?" + query; + } + } + + if (hostHeader.contains("127.0.0.1")) { + // TODO this is a bug in TCK tests, that expect localhost even though IP is sent + hostHeader = hostHeader.replace("127.0.0.1", "localhost"); + } + + // let us use host header instead of local interface + return requestUri.getScheme() + + "://" + + hostHeader + + requestUri.getPath() + + query; + } + + return requestUri.toString(); + } + + private Optional findTraced(ContainerRequestContext requestContext) { + Class definitionClass = getDefinitionClass(resourceInfo.getResourceClass()); + ExtendedUriInfo uriInfo = (ExtendedUriInfo) requestContext.getUriInfo(); + Method definitionMethod = getDefinitionMethod(requestContext, uriInfo); + + if (definitionMethod == null) { + return Optional.empty(); + } + + Traced annotation = definitionMethod.getAnnotation(Traced.class); + if (null != annotation) { + return Optional.of(annotation); + } + + return Optional.ofNullable(definitionClass.getAnnotation(Traced.class)); + } + + private Method getDefinitionMethod(ContainerRequestContext requestContext, ExtendedUriInfo uriInfo) { + ResourceMethod matchedResourceMethod = uriInfo.getMatchedResourceMethod(); + Invocable invocable = matchedResourceMethod.getInvocable(); + return invocable.getDefinitionMethod(); + } + + // taken from org.glassfish.jersey.server.model.internal.ModelHelper#getAnnotatedResourceClass + private Class getDefinitionClass(Class resourceClass) { + Class foundInterface = null; + + // traverse the class hierarchy to find the annotation + // According to specification, annotation in the super-classes must take precedence over annotation in the + // implemented interfaces + Class cls = resourceClass; + do { + if (cls.isAnnotationPresent(Path.class)) { + return cls; + } + + // if no annotation found on the class currently traversed, check for annotation in the interfaces on this + // level - if not already previously found + if (foundInterface == null) { + for (final Class i : cls.getInterfaces()) { + if (i.isAnnotationPresent(Path.class)) { + // store the interface reference in case no annotation will be found in the super-classes + foundInterface = i; + break; + } + } + } + + cls = cls.getSuperclass(); + } while (cls != null); + + if (foundInterface != null) { + return foundInterface; + } + + return resourceClass; + } } diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingInterceptor.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingInterceptor.java new file mode 100644 index 00000000000..c92902ac904 --- /dev/null +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingInterceptor.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.tracing; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Optional; + +import javax.annotation.Priority; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import javax.ws.rs.Path; + +import io.helidon.common.OptionalHelper; +import io.helidon.common.context.Context; +import io.helidon.common.context.Contexts; +import io.helidon.tracing.jersey.client.ClientTracingFilter; + +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.Tracer; +import io.opentracing.util.GlobalTracer; +import org.eclipse.microprofile.opentracing.Traced; + +import static io.helidon.common.CollectionsHelper.mapOf; + +/** + * Interceptor for {@link org.eclipse.microprofile.opentracing.Traced} annotation. + */ +@Interceptor +@Traced +@Priority(Interceptor.Priority.PLATFORM_BEFORE + 7) +public class MpTracingInterceptor { + @AroundInvoke + private Object aroundMethod(InvocationContext context) throws Exception { + Method method = context.getMethod(); + + return trace(context, method, method.getDeclaringClass()); + } + + private Object trace(InvocationContext context, + E element, + Class declaringClass) throws Exception { + if (null != declaringClass.getAnnotation(Path.class)) { + return context.proceed(); + } + + Traced annotation = element.getAnnotation(Traced.class); + if (null == annotation) { + annotation = declaringClass.getAnnotation(Traced.class); + if (null == annotation) { + return context.proceed(); + } + } + + if (annotation.value()) { + String newName = annotation.operationName(); + if (newName.isEmpty()) { + newName = spanName(declaringClass, element); + } + Tracer tracer = locateTracer(); + Optional parentSpan = locateParent(); + + Tracer.SpanBuilder spanBuilder = tracer.buildSpan(newName); + + parentSpan.ifPresent(spanBuilder::asChildOf); + + Span span = spanBuilder.start(); + try { + return context.proceed(); + } catch (Exception e) { + span.setTag("error", true); + span.log(mapOf("error.object", e.getClass().getName(), + "event", "error")); + throw e; + } finally { + span.finish(); + } + } else { + return context.proceed(); + } + } + + private String spanName(Class declaringClass, E element) { + return declaringClass.getName() + "." + element.getName(); + } + + private Optional locateParent() { + Optional context = Contexts.context(); + + return OptionalHelper.from(context.flatMap(ctx -> ctx.get(SpanContext.class))) + .or(() -> context.flatMap(ctx -> ctx.get(ClientTracingFilter.class, SpanContext.class))) + .asOptional(); + } + + private Tracer locateTracer() { + return Contexts.context() + .flatMap(ctx -> ctx.get(Tracer.class)) + .orElseGet(GlobalTracer::get); + } + +} diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java new file mode 100644 index 00000000000..ffbdf967cee --- /dev/null +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.tracing; + +import java.lang.reflect.Method; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; + +import io.helidon.tracing.jersey.client.ClientTracingFilter; + +import org.eclipse.microprofile.opentracing.Traced; + +/** + * Filter to handle REST client specifics. + */ +@Priority(Priorities.AUTHENTICATION - 350) +public class MpTracingRestClientFilter implements ClientRequestFilter { + private static final String INVOKED_METHOD = "org.eclipse.microprofile.rest.client.invokedMethod"; + + @Override + public void filter(ClientRequestContext requestContext) { + Method invokedMethod = (Method) requestContext.getProperty(INVOKED_METHOD); + + if (null == invokedMethod) { + return; + } + + Traced traced = invokedMethod.getAnnotation(Traced.class); + + if (null == traced) { + return; + } + + boolean enabled; + String opName; + + if (null != traced) { + enabled = traced.value(); + opName = traced.operationName(); + } else { + enabled = true; + opName = ""; + } + + if (!opName.isEmpty()) { + requestContext.setProperty(ClientTracingFilter.SPAN_NAME_PROPERTY_NAME, opName); + } + + requestContext.setProperty(ClientTracingFilter.ENABLED_PROPERTY_NAME, enabled); + } +} diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java new file mode 100644 index 00000000000..d145512e369 --- /dev/null +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java @@ -0,0 +1,44 @@ +package io.helidon.microprofile.tracing; + +import javax.ws.rs.Priorities; + +import io.helidon.tracing.jersey.client.ClientTracingFilter; + +import org.eclipse.microprofile.opentracing.Traced; +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.spi.RestClientListener; + +/** + * Tracing extension for Rest Client. + * Registers a filter that reads {@link org.eclipse.microprofile.opentracing.Traced} from methods to configure (or reconfigure) + * tracing. + */ +public class MpTracingRestClientListener implements RestClientListener { + private static final ClientTracingFilter FILTER = new ClientTracingFilter(); + private static final MpTracingRestClientFilter REST_CLIENT_FILTER = new MpTracingRestClientFilter(); + + @Override + public void onNewClient(Class serviceInterface, RestClientBuilder builder) { + Traced traced = serviceInterface.getAnnotation(Traced.class); + + boolean enabled; + String opName; + + if (null != traced) { + enabled = traced.value(); + opName = traced.operationName(); + } else { + enabled = true; + opName = ""; + } + + builder.register(REST_CLIENT_FILTER, Priorities.AUTHENTICATION - 300); + builder.register(FILTER, Priorities.AUTHENTICATION - 250); + builder.executorService(MpTracingClientRegistrar.EXECUTOR_SERVICE.get()); + if (!opName.isEmpty()) { + builder.property(ClientTracingFilter.SPAN_NAME_PROPERTY_NAME, opName); + } + + builder.property(ClientTracingFilter.ENABLED_PROPERTY_NAME, enabled); + } +} diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java new file mode 100644 index 00000000000..34359ae69b7 --- /dev/null +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java @@ -0,0 +1,28 @@ +package io.helidon.microprofile.tracing; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Produces; + +import io.helidon.common.context.Contexts; + +import io.opentracing.Tracer; +import io.opentracing.util.GlobalTracer; + +/** + * A producer of {@link io.opentracing.Tracer} needed for injection into {@code CDI} beans. + */ +@RequestScoped +public class TracerProducer { + + /** + * Provides an instance of tracer currently configured. + * @return a {@link Tracer} from current {@link io.helidon.common.context.Context}, + * or {@link io.opentracing.util.GlobalTracer#get()} in case we are not within a context. + */ + @Produces + public Tracer tracer() { + return Contexts.context() + .flatMap(ctx -> ctx.get(Tracer.class)) + .orElseGet(GlobalTracer::get); + } +} diff --git a/microprofile/tracing/src/main/java9/module-info.java b/microprofile/tracing/src/main/java9/module-info.java index fd2946b7ef8..f2706c05a1a 100644 --- a/microprofile/tracing/src/main/java9/module-info.java +++ b/microprofile/tracing/src/main/java9/module-info.java @@ -25,8 +25,9 @@ requires jersey.common; requires opentracing.api; - requires cdi.api; - requires javax.inject; + requires static cdi.api; + requires static javax.inject; + requires static javax.interceptor.api; requires io.helidon.microprofile.server; requires io.helidon.common; @@ -34,6 +35,9 @@ requires transitive io.helidon.tracing; requires transitive io.helidon.tracing.jersey; + requires transitive microprofile.opentracing.api; + requires microprofile.rest.client.api; + exports io.helidon.microprofile.tracing; provides io.helidon.microprofile.server.spi.MpService with io.helidon.microprofile.tracing.MpTracingService; diff --git a/microprofile/tracing/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/microprofile/tracing/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 00000000000..eaa8a4822c0 --- /dev/null +++ b/microprofile/tracing/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +io.helidon.microprofile.tracing.MpTracingCdiExtension \ No newline at end of file diff --git a/microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider b/microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider new file mode 100644 index 00000000000..d11d9ae71cf --- /dev/null +++ b/microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.opentracing.ClientTracingRegistrarProvider @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +io.helidon.microprofile.tracing.MpTracingClientRegistrar \ No newline at end of file diff --git a/microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener b/microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener new file mode 100644 index 00000000000..4a7f86273fc --- /dev/null +++ b/microprofile/tracing/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +io.helidon.microprofile.tracing.MpTracingRestClientListener \ No newline at end of file diff --git a/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable index 1380ec70301..3867a1dc00b 100644 --- a/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable +++ b/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable @@ -14,4 +14,4 @@ # limitations under the License. # -io.helidon.microprofile.tracing.TracingAutoDiscoverable \ No newline at end of file +io.helidon.microprofile.tracing.MpTracingAutoDiscoverable \ No newline at end of file diff --git a/microprofile/tracing/src/test/resources/META-INF/microprofile-config.properties b/microprofile/tracing/src/test/resources/META-INF/microprofile-config.properties new file mode 100644 index 00000000000..421b6c16f8a --- /dev/null +++ b/microprofile/tracing/src/test/resources/META-INF/microprofile-config.properties @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +tracing.service=unit-test diff --git a/pom.xml b/pom.xml index 6ed2e3e49e1..1d7b7d03411 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,8 @@ 1.1 1.1.2 2.0 + 1.3.1 + 1.2.1 2.23.4 8.0.11 5.9.3.Final @@ -1046,6 +1048,26 @@ microprofile-fault-tolerance-tck ${version.lib.microprofile-fault-tolerance-api} + + org.eclipse.microprofile.opentracing + microprofile-opentracing-api + ${version.lib.microprofile-tracing} + + + org.eclipse.microprofile.opentracing + microprofile-opentracing-tck + ${version.lib.microprofile-tracing} + + + org.eclipse.microprofile.opentracing + microprofile-opentracing-tck-rest-client + ${version.lib.microprofile-tracing} + + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + ${version.lib.microprofile-rest-client} + com.netflix.hystrix hystrix-core diff --git a/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java b/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java index 55df6076b38..2387e00d631 100644 --- a/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java +++ b/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java @@ -15,6 +15,7 @@ */ package io.helidon.tracing.jersey.client; +import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -24,6 +25,8 @@ import java.util.ServiceLoader; import java.util.stream.Collectors; +import javax.annotation.Priority; +import javax.ws.rs.Priorities; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.ClientResponseContext; @@ -93,6 +96,7 @@ * .get(); * */ +@Priority(Priorities.AUTHENTICATION - 250) public class ClientTracingFilter implements ClientRequestFilter, ClientResponseFilter { /** * Name of tracing component used to retrieve tracing configuration. @@ -102,6 +106,15 @@ public class ClientTracingFilter implements ClientRequestFilter, ClientResponseF * The {@link Tracer} property name. */ public static final String TRACER_PROPERTY_NAME = "io.helidon.tracing.tracer"; + /** + * Override name of the span created for client call. + */ + public static final String SPAN_NAME_PROPERTY_NAME = ClientTracingFilter.class.getName() + ".span-name"; + /** + * If set to false, tracing will be disabled. + * If set to true, tracing will depend on overall configuration. + */ + public static final String ENABLED_PROPERTY_NAME = ClientTracingFilter.class.getName() + ".span-enabled";; /** * The {@link SpanContext} property name. */ @@ -124,6 +137,7 @@ public class ClientTracingFilter implements ClientRequestFilter, ClientResponseF */ public static final String X_REQUEST_ID = "x-request-id"; + static final String SPAN_PROPERTY_NAME = ClientTracingFilter.class.getName() + ".span"; private static final List PROPAGATED_HEADERS = listOf(X_REQUEST_ID, X_OT_SPAN_CONTEXT); @@ -155,7 +169,7 @@ public void filter(ClientRequestContext requestContext) { Optional tracingContext = Contexts.context().flatMap(ctx -> ctx.get(TracingContext.class)); // maybe we are disabled - if (tracingDisabled(tracingContext)) { + if (tracingDisabled(requestContext, tracingContext)) { return; } @@ -168,9 +182,13 @@ public void filter(ClientRequestContext requestContext) { Optional parentSpan = findParentSpan(requestContext, tracingContext); Tracer tracer = findTracer(requestContext, tracingContext); Map> inboundHeaders = findInboundHeaders(tracingContext); + String spanName = findSpanName(requestContext, spanConfig); // create a new span for this jersey client request - Span currentSpan = createSpan(requestContext, tracer, parentSpan, spanConfig.newName().orElse(SPAN_OPERATION_NAME)); + Span currentSpan = createSpan(requestContext, + tracer, + parentSpan, + spanName); // register it so we can close the span on response requestContext.setProperty(SPAN_PROPERTY_NAME, currentSpan); @@ -199,7 +217,12 @@ public void filter(ClientRequestContext requestContext) { outboundHeaders.forEach((key, value) -> headers.put(key, new ArrayList<>(value))); } - private boolean tracingDisabled(Optional tracingContext) { + private boolean tracingDisabled(ClientRequestContext requestContext, + Optional tracingContext) { + Optional enabled = property(requestContext, Boolean.class, ENABLED_PROPERTY_NAME); + if (enabled.isPresent() && !enabled.get()) { + return true; + } return tracingContext.map(TracingContext::traceClient) .map(value -> !value) // invert, as configuration says enabled, we are interested in disabled .orElse(false); // by default enabled @@ -257,6 +280,13 @@ private Optional findParentSpan(ClientRequestContext requestContext .asOptional(); } + private String findSpanName(ClientRequestContext requestContext, SpanTracingConfig spanConfig) { + return OptionalHelper.from(property(requestContext, String.class, SPAN_NAME_PROPERTY_NAME)) + .or(spanConfig::newName) + .asOptional() + .orElseGet(() -> requestContext.getMethod().toUpperCase()); + } + private Tracer findTracer(ClientRequestContext requestContext, Optional tracingContext) { return OptionalHelper.from(property(requestContext, Tracer.class, TRACER_PROPERTY_NAME)) @@ -295,13 +325,32 @@ private Span createSpan(ClientRequestContext requestContext, Optional parentSpan, String spanName) { - Tracer.SpanBuilder spanBuilder = tracer.buildSpan(spanName) - .withTag(Tags.HTTP_METHOD.getKey(), requestContext.getMethod()) - .withTag(Tags.HTTP_URL.getKey(), requestContext.getUri().toString()); + Tracer.SpanBuilder spanBuilder = tracer.buildSpan(spanName); parentSpan.ifPresent(spanBuilder::asChildOf); - return spanBuilder.start(); + Span span = spanBuilder.start(); + + Tags.COMPONENT.set(span, "jaxrs"); + Tags.HTTP_METHOD.set(span, requestContext.getMethod()); + Tags.HTTP_URL.set(span, url(requestContext.getUri())); + Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT); + + return span; + } + + private String url(URI uri) { + String host = uri.getHost(); + host = host.replace("127.0.0.1", "localhost"); + String query = uri.getQuery(); + if (null == query) { + query = ""; + } else { + if (!query.isEmpty()) { + query = "?" + query; + } + } + return uri.getScheme() + "://" + host + ":" + uri.getPort() + uri.getPath() + query; } } diff --git a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java index 301891789ae..9a5402fd4d3 100644 --- a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java +++ b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java @@ -15,6 +15,8 @@ */ package io.helidon.tracing.jersey; +import java.net.URI; + import javax.ws.rs.ConstrainedTo; import javax.ws.rs.RuntimeType; import javax.ws.rs.container.ContainerRequestContext; @@ -32,6 +34,7 @@ import io.helidon.tracing.jersey.client.internal.TracingContext; import io.helidon.webserver.ServerRequest; +import io.opentracing.Scope; import io.opentracing.Span; import io.opentracing.SpanContext; import io.opentracing.Tracer; @@ -48,6 +51,7 @@ public abstract class AbstractTracingFilter implements ContainerRequestFilter, C * Name of the property the created span is stored in on request filter. */ protected static final String SPAN_PROPERTY = AbstractTracingFilter.class.getName() + ".span"; + protected static final String SPAN_SCOPE_PROPERTY = AbstractTracingFilter.class.getName() + ".spanScope"; @Override public void filter(ContainerRequestContext requestContext) { @@ -69,7 +73,7 @@ public void filter(ContainerRequestContext requestContext) { Tracer.SpanBuilder spanBuilder = tracer.buildSpan(spanName) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .withTag(Tags.HTTP_METHOD.getKey(), requestContext.getMethod()) - .withTag(Tags.HTTP_URL.getKey(), requestContext.getUriInfo().getRequestUri().toString()) + .withTag(Tags.HTTP_URL.getKey(), url(requestContext)) .withTag(Tags.COMPONENT.getKey(), "jaxrs"); if (null != parentSpan) { @@ -79,8 +83,10 @@ public void filter(ContainerRequestContext requestContext) { configureSpan(spanBuilder); Span span = spanBuilder.start(); + Scope spanScope = tracer.scopeManager().activate(span, false); requestContext.setProperty(SPAN_PROPERTY, span); + requestContext.setProperty(SPAN_SCOPE_PROPERTY, spanScope); context.register(ClientTracingFilter.class, span.context()); if (!context.get(TracingContext.class).isPresent()) { @@ -95,10 +101,25 @@ public void filter(ContainerRequestContext requestContext) { } } + protected String url(ContainerRequestContext requestContext) { + String hostHeader = requestContext.getHeaderString("host"); + URI requestUri = requestContext.getUriInfo().getRequestUri(); + + if (null != hostHeader) { + // let us use host header instead of local interface + return requestUri.getScheme() + + "://" + + hostHeader + + requestUri.getPath(); + } + + return requestUri.toString(); + } + @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { Span span = (Span) requestContext.getProperty(SPAN_PROPERTY); - if (span == null) { + if (null == span) { return; // not tracing } @@ -121,6 +142,11 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont Tags.HTTP_STATUS.set(span, responseContext.getStatus()); span.finish(); + + Scope spanScope = (Scope) requestContext.getProperty(SPAN_SCOPE_PROPERTY); + if (null != spanScope) { + spanScope.close(); + } } /** diff --git a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java index d334f5bc544..d438d599324 100644 --- a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java +++ b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.util.function.Function; +import javax.ws.rs.Path; import javax.ws.rs.container.ContainerRequestContext; import org.glassfish.jersey.server.ExtendedUriInfo; @@ -62,12 +63,32 @@ public static TracingHelper create(Function nam * @return name of span to use */ public static String httpPathMethodName(ContainerRequestContext requestContext) { - String path = requestContext.getUriInfo().getPath(); - if (!path.startsWith("/")) { - path = "/" + path; + Method m = getDefinitionMethod(requestContext); + // TODO maybe use UriBuilder + Path methodPath = m.getAnnotation(Path.class); + Path resourcePath = m.getDeclaringClass().getAnnotation(Path.class); + + StringBuilder fullPath = new StringBuilder(); + fullPath.append(requestContext.getMethod().toUpperCase()); + fullPath.append(":"); + + if (null != resourcePath) { + String resourcePathS = resourcePath.value(); + if (!resourcePathS.startsWith("/")) { + fullPath.append("/"); + } + fullPath.append(resourcePath.value()); } - return requestContext.getMethod() - + ":" + path; + if (null != methodPath) { + String methodPathS = methodPath.value(); + + if ((fullPath.length() != 0) && (fullPath.charAt(fullPath.length() - 1) != '/') && !methodPathS.startsWith("/")) { + fullPath.append("/"); + } + fullPath.append(methodPath.value()); + } + + return fullPath.toString(); } /** From d35cb6e075ef9f303efeefa9e96ad6db8e3e0185 Mon Sep 17 00:00:00 2001 From: David Kral Date: Mon, 8 Jul 2019 11:48:43 +0200 Subject: [PATCH 2/9] Rest client basic documentation added Copyright checks and codestyle errors fixed Signed-off-by: David Kral --- .../docs/microprofile/09_rest-client.adoc | 91 ++++++++++--------- microprofile/rest-client/pom.xml | 5 + .../tracing/MpTracingCdiExtension.java | 15 +++ .../microprofile/tracing/MpTracingFilter.java | 2 +- .../tracing/MpTracingRestClientListener.java | 15 +++ .../microprofile/tracing/TracerProducer.java | 15 +++ ...sfish.jersey.internal.spi.AutoDiscoverable | 2 +- .../jersey/client/ClientTracingFilter.java | 2 +- .../tracing/jersey/AbstractTracingFilter.java | 15 ++- .../helidon/tracing/jersey/TracingHelper.java | 3 +- 10 files changed, 114 insertions(+), 51 deletions(-) diff --git a/docs/src/main/docs/microprofile/09_rest-client.adoc b/docs/src/main/docs/microprofile/09_rest-client.adoc index 9d22f16c9ba..8a8a822006b 100644 --- a/docs/src/main/docs/microprofile/09_rest-client.adoc +++ b/docs/src/main/docs/microprofile/09_rest-client.adoc @@ -42,67 +42,76 @@ Example: ---- SomeInterface someInterface = RestClientBuilder.newBuilder() - .baseUri("localhost:8080") + .baseUri(URI.create("http://localhost:8080/baseUri")) .build(SomeInterface.class); -remoteApi.someMethod(apiModel); +someInterface.someMethod(apiModel); ---- -== Creating new client - Annotation -It is also possible to create rest client via annotations. - - -MicroProfile Rest Client can be created by using RestClientBuilder class -and calling method newBuilder. Then you need to specify the baseUri you want to -target on and then just call method build. This method accepts only one -parameter and it is an interface you want to create client from. +== Creating new client - CDI +It is also possible to create rest client via CDI. Interface +which you want to create new client from has to be annotated with +`@RegisterRestClient` annotation. This particular annotation has optional +parameter `baseUri` which does the same thing as baseUri method on builder. Example: [source,java] ---- -SomeInterface someInterface = - RestClientBuilder.newBuilder() - .baseUri("localhost:8080") - .build(SomeInterface.class); -remoteApi.someMethod(apiModel); ----- +@RegisterRestClient(baseUri="http://localhost:8080/baseUri") +public interface SomeResource { -== Trace propagation across services -Automated trace propagation is supported currently only with Jersey client. - -Tracing propagation works automatically as long as you execute -the client call in the same thread as the Jersey server side. +// ... -_Exceptions that require manual handling_: +} +---- -- When the resource method is annotated with Fault Tolerance annotations (e.g. `@Fallback`) -- When you use `async()` on the Jersey client request +Another step which you need to specify is to create actual injection point +for this resource to be injected in. -In such cases, you must provide the SpanContext by hand: +Example: [source,java] -.Tracing propagation with Jersey client (on a different thread) ---- -import static io.helidon.tracing.jersey.client.ClientTracingFilter.CURRENT_SPAN_CONTEXT_PROPERTY_NAME; -import static io.helidon.tracing.jersey.client.ClientTracingFilter.TRACER_PROPERTY_NAME; +@Inject +@RestClient +SomeResource client; // ... -Response response = client.target(serviceEndpoint) - .request() - // tracer should be provided unless available as GlobalTracer - .property(TRACER_PROPERTY_NAME, tracer) - .property(CURRENT_SPAN_CONTEXT_PROPERTY_NAME, spanContext) - .get(); +client.sampleMethod(); + ---- -Tracer and SpanContext can be obtained from `ServerRequest` that can be injected into Resource classes: +== Rest client configuration +Rest client implementation allows you to configure its parameters by builder, +annotations and configuration. By using each of these, it is possible to register +new providers and their priorities, specify base uri/url and others. +To further investigation of how to do that, please take a look at the spec: +`https://download.eclipse.org/microprofile/microprofile-rest-client-1.2.1/microprofile-rest-client-1.2.1.html` + +== Runnable example +To be able to run and test this example, please head to the Helidon examples/quickstarts +and start helidon-quickstart-mp example by its Main.main method. Then create project with +the dependency on the Helidon rest client implementation and create following rest client +interface. + +Rest client interface: +[source,java] ---- -@Context -io.helidon.webserver.ServerRequest serverRequest; +@Path("/greet") +interface GreetRestClient { -//... + @GET + JsonObject getDefaultMessage(); -SpanContext spanContext = serverRequest.spanContext(); -// optional, you could also use GlobalTracer.get() if it is configured -Tracer tracer = req.webServer().configuration().tracer(); + @Path("/{name}") + @GET + JsonObject getMessage(@PathParam("name") String name); + +} ---- +Then create runnable method the same way as it is described in +`Creating new client - Interface`,but with baseUri `http://localhost:8080/greet` +and different interface name. + +When you try to run the methods on your new instance, you should be getting output +as expected. \ No newline at end of file diff --git a/microprofile/rest-client/pom.xml b/microprofile/rest-client/pom.xml index 9390a439268..f4ecd8b6535 100644 --- a/microprofile/rest-client/pom.xml +++ b/microprofile/rest-client/pom.xml @@ -30,6 +30,11 @@ helidon-microprofile-rest-client Helidon Microprofile Rest Client + + + true + + org.glassfish.jersey.ext.microprofile diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java index 62484e766df..115ddf1c8df 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingCdiExtension.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.tracing; import javax.enterprise.event.Observes; diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java index e997d370c22..90ee586e4e3 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java index d145512e369..cfd2833a7fc 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.tracing; import javax.ws.rs.Priorities; diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java index 34359ae69b7..c7800ee9555 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/TracerProducer.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.tracing; import javax.enterprise.context.RequestScoped; diff --git a/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable index 3867a1dc00b..52aa3d978b1 100644 --- a/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable +++ b/microprofile/tracing/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable @@ -1,5 +1,5 @@ # -# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java b/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java index 2387e00d631..279f497a7cd 100644 --- a/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java +++ b/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java @@ -114,7 +114,7 @@ public class ClientTracingFilter implements ClientRequestFilter, ClientResponseF * If set to false, tracing will be disabled. * If set to true, tracing will depend on overall configuration. */ - public static final String ENABLED_PROPERTY_NAME = ClientTracingFilter.class.getName() + ".span-enabled";; + public static final String ENABLED_PROPERTY_NAME = ClientTracingFilter.class.getName() + ".span-enabled"; /** * The {@link SpanContext} property name. */ diff --git a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java index 9a5402fd4d3..18f54992b51 100644 --- a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java +++ b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/AbstractTracingFilter.java @@ -47,11 +47,9 @@ @ConstrainedTo(RuntimeType.SERVER) @PreMatching public abstract class AbstractTracingFilter implements ContainerRequestFilter, ContainerResponseFilter { - /** - * Name of the property the created span is stored in on request filter. - */ - protected static final String SPAN_PROPERTY = AbstractTracingFilter.class.getName() + ".span"; - protected static final String SPAN_SCOPE_PROPERTY = AbstractTracingFilter.class.getName() + ".spanScope"; + + private static final String SPAN_PROPERTY = AbstractTracingFilter.class.getName() + ".span"; + private static final String SPAN_SCOPE_PROPERTY = AbstractTracingFilter.class.getName() + ".spanScope"; @Override public void filter(ContainerRequestContext requestContext) { @@ -101,6 +99,13 @@ public void filter(ContainerRequestContext requestContext) { } } + /** + * Resolves host name based on the "host" header. If this header is not set, then + * {@link URI#toString()} is called. + * + * @param requestContext request context + * @return resolved url + */ protected String url(ContainerRequestContext requestContext) { String hostHeader = requestContext.getHeaderString("host"); URI requestUri = requestContext.getUriInfo().getRequestUri(); diff --git a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java index d438d599324..5abceff701f 100644 --- a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java +++ b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,7 +64,6 @@ public static TracingHelper create(Function nam */ public static String httpPathMethodName(ContainerRequestContext requestContext) { Method m = getDefinitionMethod(requestContext); - // TODO maybe use UriBuilder Path methodPath = m.getAnnotation(Path.class); Path resourcePath = m.getDeclaringClass().getAnnotation(Path.class); From 30adec60e61599c2243fcb5aa5a648ca4cf84462 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 9 Jul 2019 17:13:31 +0200 Subject: [PATCH 3/9] Added jersey common to remove duplicate code Signed-off-by: Tomas Langer --- .../docs/microprofile/01_introduction.adoc | 2 +- .../docs/microprofile/09_rest-client.adoc | 57 ++-- jersey/common/pom.xml | 59 ++++ .../jersey/common/InvokedResource.java | 116 +++++++ .../jersey/common/InvokedResourceImpl.java | 316 ++++++++++++++++++ .../helidon/jersey/common/package-info.java | 13 +- jersey/common/src/main/java9/module-info.java | 31 ++ .../common/InvokedResourceImplTest.java | 290 ++++++++++++++++ jersey/pom.xml | 1 + microprofile/tracing/pom.xml | 5 + .../microprofile/tracing/MpTracingFilter.java | 75 +---- .../tracing/src/main/java9/module-info.java | 1 + .../tracing/TestTracerProvider.java | 26 +- .../microprofile/tracing/TracingTest.java | 6 +- security/integration/jersey/pom.xml | 5 + .../integration/jersey/SecurityFilter.java | 100 ++---- .../jersey/src/main/java9/module-info.java | 1 + .../jersey/OptionalSecurityTest.java | 16 - tracing/jaeger/pom.xml | 9 +- .../jersey/client/ClientTracingFilter.java | 7 +- tracing/jersey/pom.xml | 5 + .../helidon/tracing/jersey/TracingHelper.java | 74 ++-- .../jersey/src/main/java9/module-info.java | 1 + 23 files changed, 967 insertions(+), 249 deletions(-) create mode 100644 jersey/common/pom.xml create mode 100644 jersey/common/src/main/java/io/helidon/jersey/common/InvokedResource.java create mode 100644 jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java rename microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingFilterTest.java => jersey/common/src/main/java/io/helidon/jersey/common/package-info.java (72%) create mode 100644 jersey/common/src/main/java9/module-info.java create mode 100644 jersey/common/src/test/java/io/helidon/jersey/common/InvokedResourceImplTest.java diff --git a/docs/src/main/docs/microprofile/01_introduction.adoc b/docs/src/main/docs/microprofile/01_introduction.adoc index ad5fc34c022..784335c1d46 100644 --- a/docs/src/main/docs/microprofile/01_introduction.adoc +++ b/docs/src/main/docs/microprofile/01_introduction.adoc @@ -51,7 +51,7 @@ Then declare the following dependency in your project: ---- io.helidon.microprofile.bundles - helidon-microprofile-1.2 + helidon-microprofile-2.2 ---- diff --git a/docs/src/main/docs/microprofile/09_rest-client.adoc b/docs/src/main/docs/microprofile/09_rest-client.adoc index 8a8a822006b..ad0f5ef5451 100644 --- a/docs/src/main/docs/microprofile/09_rest-client.adoc +++ b/docs/src/main/docs/microprofile/09_rest-client.adoc @@ -21,37 +21,38 @@ = :keywords: helidon, rest, client, microprofile, micro-profile == Configuring Rest Client with Helidon MP -Declare the following dependency in your project: +MicroProfile Rest Client adds the capability to invoke remote microservices using a JAX-RS like interface to declare the +operations. + +To use the rest client in your project, declare the following dependency: [source,xml] ---- - io.helidon.microprofile.restclient + io.helidon.microprofile.rest-client helidon-microprofile-rest-client ---- -== Creating new client - Interface -MicroProfile Rest Client can be created by using RestClientBuilder class -and calling method newBuilder. Then you need to specify the baseUri you want to -target on and then just call method build. This method accepts only one -parameter and it is an interface you want to create client from. +== Creating a new client using a builder + +MicroProfile Rest Client can be created using a builder obtained from `RestClientBuilder.newBuilder()`. +The builder provides methods to configure details for the client and to define the desired rest client interface. Example: [source,java] ---- -SomeInterface someInterface = - RestClientBuilder.newBuilder() +SomeResource someResource = RestClientBuilder.newBuilder() .baseUri(URI.create("http://localhost:8080/baseUri")) - .build(SomeInterface.class); -someInterface.someMethod(apiModel); + .build(SomeResource.class); + +someResource.someMethod(apiModel); ---- == Creating new client - CDI -It is also possible to create rest client via CDI. Interface -which you want to create new client from has to be annotated with -`@RegisterRestClient` annotation. This particular annotation has optional -parameter `baseUri` which does the same thing as baseUri method on builder. +A rest client interface can be annotated with `@RegisterRestClient` to automatically register it with CDI. +The `RegisterRestClient` annotation has a property `baseUri` that can be used to define the base endpoint of this client. +This value can be overridden using configuration. Example: [source,java] @@ -64,8 +65,7 @@ public interface SomeResource { } ---- -Another step which you need to specify is to create actual injection point -for this resource to be injected in. +Once a rest client interface is annotated, it can be injected into any CDI bean. Example: [source,java] @@ -82,19 +82,19 @@ client.sampleMethod(); == Rest client configuration Rest client implementation allows you to configure its parameters by builder, -annotations and configuration. By using each of these, it is possible to register -new providers and their priorities, specify base uri/url and others. +annotations, and configuration. -To further investigation of how to do that, please take a look at the spec: -`https://download.eclipse.org/microprofile/microprofile-rest-client-1.2.1/microprofile-rest-client-1.2.1.html` +You can configure new providers, base URI/URL and other options of the client. +See specification for full details: +https://download.eclipse.org/microprofile/microprofile-rest-client-1.2.1/microprofile-rest-client-1.2.1.html -== Runnable example +== Quickstart example To be able to run and test this example, please head to the Helidon examples/quickstarts -and start helidon-quickstart-mp example by its Main.main method. Then create project with -the dependency on the Helidon rest client implementation and create following rest client -interface. +and start the helidon-quickstart-mp. Then create project with +the dependency on the Helidon rest client implementation and create the following rest client +interface: -Rest client interface: +Rest client interface [source,java] ---- @Path("/greet") @@ -111,7 +111,6 @@ interface GreetRestClient { ---- Then create runnable method the same way as it is described in `Creating new client - Interface`,but with baseUri `http://localhost:8080/greet` -and different interface name. +and the above interface. -When you try to run the methods on your new instance, you should be getting output -as expected. \ No newline at end of file +By calling `GreetRestClient.getDefaultMessage()` you reach the endpoint of Helidon quickstart. \ No newline at end of file diff --git a/jersey/common/pom.xml b/jersey/common/pom.xml new file mode 100644 index 00000000000..c2f65b39be5 --- /dev/null +++ b/jersey/common/pom.xml @@ -0,0 +1,59 @@ + + + + + + helidon-jersey-project + io.helidon.jersey + 1.1.3-SNAPSHOT + + 4.0.0 + + helidon-jersey-common + Helidon Jersey Common + + + + io.helidon.jersey + helidon-jersey-server + ${project.version} + provided + + + io.helidon.common + helidon-common + ${project.version} + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + org.mockito + mockito-core + test + + + \ No newline at end of file diff --git a/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResource.java b/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResource.java new file mode 100644 index 00000000000..2889780c3ca --- /dev/null +++ b/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResource.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.jersey.common; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Optional; + +import javax.ws.rs.container.ContainerRequestContext; + +/** + * Information about the current request - invoked resource information. + */ +public interface InvokedResource { + /** + * Create a new invoked resource from Jersey container request context. + * + * @param context request context + * @return an instance of invoked resource to access information about resource class, method and annotations + */ + static InvokedResource create(ContainerRequestContext context) { + return InvokedResourceImpl.create(context); + } + + /** + * Method that defines the invoked resource method. + * This may come from an interface. + * + * @return Method used to declared handling of the current request or empty if none found + */ + Optional definitionMethod(); + + /** + * Method that handles the invoked resource method. + * This must come from a class. + * + * @return Method used to handle current request or empty if none found + */ + Optional handlingMethod(); + + /** + * Resource definition class. + * The definition class is the class annotated with {@link javax.ws.rs.Path} + * annotation. + * + * @return class of the JAX-RS resource or empty if none found + */ + Optional> definitionClass(); + + /** + * Resource handling class. + * The handling class is the class that declares the handling method. + * + * @return class of the JAX-RS resource implementation or empty if none found + */ + Optional> handlingClass(); + + /** + * Find the annotation by class closest to the handling method. + *

+ * Search order: + *

    + *
  1. {@link #handlingMethod()}
  2. + *
  3. All methods from super classes up to {@link #definitionMethod()}
  4. + *
  5. {@link #handlingClass()}
  6. + *
  7. All super classes of the {@link #handlingClass()}
  8. + *
  9. All implemented interfaces
  10. + *
+ * @param annotationClass class of the annotation to find + * @param type of the annotation + * @return first annotation found, or empty if not declared + */ + Optional findAnnotation(Class annotationClass); + + /** + * Find method annotation by class closest to the handling method. + *

+ * Search order: + *

    + *
  1. {@link #handlingMethod()}
  2. + *
  3. All methods from super classes up to {@link #definitionMethod()}
  4. + *
+ * @param annotationClass class of the annotation to find + * @param type of the annotation + * @return first annotation found, or empty if not declared on a method + */ + Optional findMethodAnnotation(Class annotationClass); + + /** + * Find class annotation by class closest to the handling class. + *

+ * Search order: + *

    + *
  1. {@link #handlingClass()}
  2. + *
  3. All super classes of the {@link #handlingClass()}
  4. + *
  5. All implemented interfaces
  6. + *
+ * @param annotationClass class of the annotation to find + * @param type of the annotation + * @return first annotation found, or empty if not declared on a class + */ + Optional findClassAnnotation(Class annotationClass); +} diff --git a/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java b/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java new file mode 100644 index 00000000000..ce8ed2fef3c --- /dev/null +++ b/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.jersey.common; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.Path; +import javax.ws.rs.container.ContainerRequestContext; + +import io.helidon.common.OptionalHelper; + +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.model.Invocable; +import org.glassfish.jersey.server.model.ResourceMethod; + +final class InvokedResourceImpl implements InvokedResource { + private static final ConcurrentHashMap> METHOD_ANNOTATION_CACHE = + new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> CLASS_ANNOTATION_CACHE = + new ConcurrentHashMap<>(); + + private final Optional> definitionClass; + private final Optional> handlingClass; + private final Optional definitionMethod; + private final Optional handlingMethod; + + private InvokedResourceImpl(Optional definitionMethod, + Optional handlingMethod, + Optional> handlingClass, + Optional> definitionClass) { + + this.definitionMethod = definitionMethod; + this.handlingMethod = handlingMethod; + this.handlingClass = handlingClass; + this.definitionClass = definitionClass; + } + + static InvokedResource create(ContainerRequestContext context) { + ExtendedUriInfo uriInfo = (ExtendedUriInfo) context.getUriInfo(); + ResourceMethod matchedResourceMethod = uriInfo.getMatchedResourceMethod(); + Invocable invocable = matchedResourceMethod.getInvocable(); + + Class handlingClass = invocable.getHandler().getHandlerClass(); + Method handlingMethod = invocable.getHandlingMethod(); + + Class definitionClass = getDefinitionClass(handlingClass); + Method definitionMethod = invocable.getDefinitionMethod(); + + return new InvokedResourceImpl( + Optional.ofNullable(definitionMethod), + Optional.ofNullable(handlingMethod), + Optional.ofNullable(handlingClass), + Optional.ofNullable(definitionClass) + ); + } + + @Override + public Optional definitionMethod() { + return definitionMethod; + } + + @Override + public Optional handlingMethod() { + return handlingMethod; + } + + @Override + public Optional> definitionClass() { + return definitionClass; + } + + @Override + public Optional> handlingClass() { + return handlingClass; + } + + @Override + public Optional findAnnotation(Class annotationClass) { + return OptionalHelper.from(findMethodAnnotation(annotationClass)) + .or(() -> findClassAnnotation(annotationClass)) + .asOptional(); + + } + + @Override + @SuppressWarnings("unchecked") + public Optional findMethodAnnotation(Class annotationClass) { + if (!handlingMethod.isPresent() || !handlingClass.isPresent()) { + return Optional.empty(); + } + + Method theMethod = handlingMethod().get(); + Class theClass = handlingClass.get(); + Class definitionClass = definitionClass().orElse(theClass); + + return (Optional) METHOD_ANNOTATION_CACHE + .computeIfAbsent(new MethodAnnotationKey(annotationClass, definitionClass, theClass, theMethod), + aKey -> findMethodAnnotation(annotationClass, + theClass, + theMethod)); + + } + + @SuppressWarnings("unchecked") + @Override + public Optional findClassAnnotation(Class annotationClass) { + if (!handlingClass.isPresent()) { + return Optional.empty(); + } + + Class theClass = handlingClass.get(); + + return (Optional) CLASS_ANNOTATION_CACHE.computeIfAbsent(new ClassAnnotationKey(annotationClass, theClass), + aKey -> findClassAnnotation(annotationClass, + theClass)); + } + + private Optional findClassAnnotation(Class annotationClass, + Class theClass) { + + List> hierarchy = hierarchy(theClass); + + // find in hierarchy of the classes + for (Class aClass : hierarchy) { + T annot = aClass.getDeclaredAnnotation(annotationClass); + if (null != annot) { + return Optional.of(annot); + } + } + + return Optional.empty(); + } + + private Optional findMethodAnnotation(Class annotationClass, + Class theClass, + Method theMethod) { + List> hierarchy = hierarchy(theClass); + + // find annotations in the hierarchy of methods + String name = theMethod.getName(); + Class[] parameterTypes = theMethod.getParameterTypes(); + + for (Class aClass : hierarchy) { + try { + Method method = aClass.getDeclaredMethod(name, parameterTypes); + T annot = method.getDeclaredAnnotation(annotationClass); + if (null != annot) { + return Optional.of(annot); + } + } catch (NoSuchMethodException ignore) { + // ignore as the method may not be declared on some of the super classes or interfaces + } + } + + return Optional.empty(); + } + + private static final class ClassAnnotationKey { + private final Class annotationClass; + private final Class handlingClass; + + private ClassAnnotationKey(Class annotationClass, Class handlingClass) { + this.annotationClass = annotationClass; + this.handlingClass = handlingClass; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClassAnnotationKey)) { + return false; + } + ClassAnnotationKey that = (ClassAnnotationKey) o; + return annotationClass.equals(that.annotationClass) && + handlingClass.equals(that.handlingClass); + } + + @Override + public int hashCode() { + return Objects.hash(annotationClass, handlingClass); + } + } + + private static final class MethodAnnotationKey { + private final Class annotationClass; + private final Class definitionClass; + private final Class handlingClass; + private final Method handlingMethod; + + private MethodAnnotationKey(Class annotationClass, + Class definitionClass, + Class handlingClass, + Method handlingMethod) { + + this.annotationClass = annotationClass; + this.definitionClass = definitionClass; + this.handlingClass = handlingClass; + this.handlingMethod = handlingMethod; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MethodAnnotationKey)) { + return false; + } + MethodAnnotationKey that = (MethodAnnotationKey) o; + return annotationClass.equals(that.annotationClass) && + definitionClass.equals(that.definitionClass) && + handlingClass.equals(that.handlingClass) && + handlingMethod.equals(that.handlingMethod); + } + + @Override + public int hashCode() { + return Objects.hash(annotationClass, definitionClass, handlingClass, handlingMethod); + } + } + + private static List> hierarchy(Class aClass) { + List> result = new LinkedList<>(); + Set> processed = new HashSet<>(); + + // add the processed class + result.add(aClass); + // first all superclasses + Class current = aClass.getSuperclass(); + while (!Object.class.equals(current)) { + if (processed.add(current)) { + result.add(current); + } + current = current.getSuperclass(); + } + + List> interfaces = new LinkedList<>(); + + // then all interfaces + result.forEach(clazz -> { + addInterfaces(clazz, interfaces, processed); + }); + + result.addAll(interfaces); + + return result; + } + + private static void addInterfaces(Class clazz, List> interfaces, Set> processed) { + Class[] found = clazz.getInterfaces(); + for (Class anInterface : found) { + if (processed.add(anInterface)) { + interfaces.add(anInterface); + addInterfaces(anInterface, interfaces, processed); + } + } + } + + // taken from org.glassfish.jersey.server.model.internal.ModelHelper#getAnnotatedResourceClass + private static Class getDefinitionClass(Class resourceClass) { + Class foundInterface = null; + + // traverse the class hierarchy to find the annotation + // According to specification, annotation in the super-classes must take precedence over annotation in the + // implemented interfaces + Class cls = resourceClass; + do { + if (cls.isAnnotationPresent(Path.class)) { + return cls; + } + + // if no annotation found on the class currently traversed, check for annotation in the interfaces on this + // level - if not already previously found + if (foundInterface == null) { + for (final Class i : cls.getInterfaces()) { + if (i.isAnnotationPresent(Path.class)) { + // store the interface reference in case no annotation will be found in the super-classes + foundInterface = i; + break; + } + } + } + + cls = cls.getSuperclass(); + } while (cls != null); + + if (foundInterface != null) { + return foundInterface; + } + + return resourceClass; + } +} diff --git a/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingFilterTest.java b/jersey/common/src/main/java/io/helidon/jersey/common/package-info.java similarity index 72% rename from microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingFilterTest.java rename to jersey/common/src/main/java/io/helidon/jersey/common/package-info.java index 89c4a71d321..6326344410a 100644 --- a/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingFilterTest.java +++ b/jersey/common/src/main/java/io/helidon/jersey/common/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package io.helidon.microprofile.tracing; - -import io.helidon.tracing.jersey.TracingFilter; - /** - * Unit test for {@link TracingFilter}. + * Utilities for Jersey handling. */ -class TracingFilterTest { - -} \ No newline at end of file +package io.helidon.jersey.common; diff --git a/jersey/common/src/main/java9/module-info.java b/jersey/common/src/main/java9/module-info.java new file mode 100644 index 00000000000..327b8566a2a --- /dev/null +++ b/jersey/common/src/main/java9/module-info.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +/** + * Eclipse Microprofile Tracing implementation for helidon microprofile. + */ +module io.helidon.jersey.common { + requires java.logging; + requires java.annotation; + + requires io.helidon.common; + + requires java.ws.rs; + requires jersey.common; + requires jersey.server; + + exports io.helidon.jersey.common; +} diff --git a/jersey/common/src/test/java/io/helidon/jersey/common/InvokedResourceImplTest.java b/jersey/common/src/test/java/io/helidon/jersey/common/InvokedResourceImplTest.java new file mode 100644 index 00000000000..62473f55f0f --- /dev/null +++ b/jersey/common/src/test/java/io/helidon/jersey/common/InvokedResourceImplTest.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.jersey.common; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Optional; + +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.Path; +import javax.ws.rs.container.ContainerRequestContext; + +import org.glassfish.jersey.server.ExtendedUriInfo; +import org.glassfish.jersey.server.model.Resource; +import org.glassfish.jersey.server.model.ResourceMethod; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Unit test for {@link InvokedResourceImpl}. + */ +class InvokedResourceImplTest { + private static Method anInterfaceMethod; + private static Method topLevelMethod; + private static Method topLevelImplementingNoAnnot; + private static Method secondMethod; + private static Method secondImplementingMethod; + private static Method secondImplementingNoAnnotMethod; + + @BeforeAll + static void initClass() throws NoSuchMethodException { + anInterfaceMethod = AnInterface.class.getMethod("aMethod"); + topLevelMethod = TopLevel.class.getMethod("aMethod"); + topLevelImplementingNoAnnot = TopLevelImplementingNoAnnot.class.getMethod("aMethod"); + secondMethod = Second.class.getMethod("aMethod"); + secondImplementingMethod = SecondImplement.class.getMethod("aMethod"); + secondImplementingNoAnnotMethod = SecondImplementNoAnnot.class.getMethod("aMethod"); + } + + @Test + void testTopLevel() { + Class handlingClass = TopLevel.class; + + Resource.Builder resourceBuilder = Resource.builder(TopLevel.class); + + // final class + ResourceMethod method = resourceBuilder.addMethod() + .handledBy(handlingClass, topLevelMethod) + .build(); + + ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class); + when(uriInfo.getMatchedResourceMethod()).thenReturn(method); + + ContainerRequestContext requestContext = mock(ContainerRequestContext.class); + when(requestContext.getUriInfo()).thenReturn(uriInfo); + + InvokedResource invokedResource = InvokedResourceImpl.create(requestContext); + + assertThat(invokedResource.definitionMethod(), is(Optional.of(topLevelMethod))); + assertThat(invokedResource.handlingMethod(), is(Optional.of(topLevelMethod))); + assertThat(invokedResource.definitionClass(), is(Optional.of(TopLevel.class))); + assertThat(invokedResource.handlingClass(), is(Optional.of(TopLevel.class))); + assertThat(invokedResource.findAnnotation(Path.class), is(Optional.of(path("TopLevel.aMethod")))); + assertThat(invokedResource.findMethodAnnotation(Path.class), is(Optional.of(path("TopLevel.aMethod")))); + assertThat(invokedResource.findClassAnnotation(Path.class), is(Optional.of(path("TopLevel")))); + assertThat(invokedResource.findClassAnnotation(RolesAllowed.class), is(Optional.empty())); + } + + @Test + void testTopLevelImplementingNoAnnot() { + Class handlingClass = TopLevelImplementingNoAnnot.class; + + Resource.Builder resourceBuilder = Resource.builder(AnInterface.class); + + // final class + ResourceMethod method = resourceBuilder.addMethod() + .handledBy(handlingClass, anInterfaceMethod) + .build(); + + ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class); + when(uriInfo.getMatchedResourceMethod()).thenReturn(method); + + ContainerRequestContext requestContext = mock(ContainerRequestContext.class); + when(requestContext.getUriInfo()).thenReturn(uriInfo); + + InvokedResource invokedResource = InvokedResourceImpl.create(requestContext); + + assertThat(invokedResource.definitionMethod(), is(Optional.of(anInterfaceMethod))); + assertThat(invokedResource.handlingMethod(), is(Optional.of(topLevelImplementingNoAnnot))); + assertThat(invokedResource.definitionClass(), is(Optional.of(AnInterface.class))); + assertThat(invokedResource.handlingClass(), is(Optional.of(TopLevelImplementingNoAnnot.class))); + assertThat(invokedResource.findAnnotation(Path.class), is(Optional.of(path("AnInterface.aMethod")))); + assertThat(invokedResource.findMethodAnnotation(Path.class), is(Optional.of(path("AnInterface.aMethod")))); + assertThat(invokedResource.findClassAnnotation(Path.class), is(Optional.of(path("AnInterface")))); + assertThat(invokedResource.findClassAnnotation(RolesAllowed.class), is(Optional.empty())); + } + + @Test + void testSecond() { + Class handlingClass = Second.class; + Method handlingMethod = secondMethod; + Class definitionClass = TopLevel.class; + Method definitionMethod = secondMethod; + + Resource.Builder resourceBuilder = Resource.builder(definitionClass); + + // final class + ResourceMethod method = resourceBuilder.addMethod() + .handledBy(handlingClass, handlingMethod) + .build(); + + ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class); + when(uriInfo.getMatchedResourceMethod()).thenReturn(method); + + ContainerRequestContext requestContext = mock(ContainerRequestContext.class); + when(requestContext.getUriInfo()).thenReturn(uriInfo); + + InvokedResource invokedResource = InvokedResourceImpl.create(requestContext); + + assertThat(invokedResource.definitionMethod(), is(Optional.of(definitionMethod))); + assertThat(invokedResource.handlingMethod(), is(Optional.of(handlingMethod))); + assertThat(invokedResource.definitionClass(), is(Optional.of(definitionClass))); + assertThat(invokedResource.handlingClass(), is(Optional.of(handlingClass))); + assertThat(invokedResource.findAnnotation(Path.class), is(Optional.of(path("Second.aMethod")))); + assertThat(invokedResource.findMethodAnnotation(Path.class), is(Optional.of(path("Second.aMethod")))); + assertThat(invokedResource.findClassAnnotation(Path.class), is(Optional.of(path("TopLevel")))); + assertThat(invokedResource.findClassAnnotation(RolesAllowed.class), is(Optional.empty())); + } + + @Test + void testSecondImplement() { + Class handlingClass = SecondImplement.class; + Method handlingMethod = secondImplementingMethod; + Class definitionClass = AnInterface.class; + Method definitionMethod = secondImplementingMethod; + + Resource.Builder resourceBuilder = Resource.builder(definitionClass); + + // final class + ResourceMethod method = resourceBuilder.addMethod() + .handledBy(handlingClass, handlingMethod) + .build(); + + ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class); + when(uriInfo.getMatchedResourceMethod()).thenReturn(method); + + ContainerRequestContext requestContext = mock(ContainerRequestContext.class); + when(requestContext.getUriInfo()).thenReturn(uriInfo); + + InvokedResource invokedResource = InvokedResourceImpl.create(requestContext); + + assertThat(invokedResource.definitionMethod(), is(Optional.of(definitionMethod))); + assertThat(invokedResource.handlingMethod(), is(Optional.of(handlingMethod))); + assertThat(invokedResource.definitionClass(), is(Optional.of(definitionClass))); + assertThat(invokedResource.handlingClass(), is(Optional.of(handlingClass))); + assertThat(invokedResource.findAnnotation(Path.class), is(Optional.of(path("SecondImplement.aMethod")))); + assertThat(invokedResource.findMethodAnnotation(Path.class), is(Optional.of(path("SecondImplement.aMethod")))); + assertThat(invokedResource.findClassAnnotation(Path.class), is(Optional.of(path("AnInterface")))); + assertThat(invokedResource.findClassAnnotation(RolesAllowed.class), is(Optional.empty())); + } + + @Test + void testSecondImplementNoAnnot() { + Class handlingClass = SecondImplementNoAnnot.class; + Method handlingMethod = secondImplementingNoAnnotMethod; + Class definitionClass = AnInterface.class; + Method definitionMethod = topLevelImplementingNoAnnot; + + Resource.Builder resourceBuilder = Resource.builder(definitionClass); + + // final class + ResourceMethod method = resourceBuilder.addMethod() + .handledBy(handlingClass, handlingMethod) + .build(); + + ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class); + when(uriInfo.getMatchedResourceMethod()).thenReturn(method); + + ContainerRequestContext requestContext = mock(ContainerRequestContext.class); + when(requestContext.getUriInfo()).thenReturn(uriInfo); + + InvokedResource invokedResource = InvokedResourceImpl.create(requestContext); + + assertThat(invokedResource.definitionMethod(), is(Optional.of(definitionMethod))); + assertThat(invokedResource.handlingMethod(), is(Optional.of(handlingMethod))); + assertThat(invokedResource.definitionClass(), is(Optional.of(definitionClass))); + assertThat(invokedResource.handlingClass(), is(Optional.of(handlingClass))); + assertThat(invokedResource.findAnnotation(Path.class), is(Optional.of(path("AnInterface.aMethod")))); + assertThat(invokedResource.findMethodAnnotation(Path.class), is(Optional.of(path("AnInterface.aMethod")))); + assertThat(invokedResource.findClassAnnotation(Path.class), is(Optional.of(path("AnInterface")))); + assertThat(invokedResource.findClassAnnotation(RolesAllowed.class), is(Optional.empty())); + } + + + private Path path(String path) { + return new Path() { + @Override + public String value() { + return path; + } + + @Override + public Class annotationType() { + return Path.class; + } + + @Override + public String toString() { + return path; + } + }; + } + + @Path("AnInterface") + private interface AnInterface { + @Path("AnInterface.aMethod") + @RolesAllowed("AnInterface.aMethod") + String aMethod(); + } + + @Path("TopLevel") + private static class TopLevel { + @Path("TopLevel.aMethod") + public String aMethod() { + return "TopLevel.aMethod"; + } + } + + @Path("TopLevelClassAnnot") + private static class TopLevelClassAnnot { + public String aMethod() { + return "TopLevel.aMethod"; + } + } + + private static class TopLevelImplementing implements AnInterface { + @Path("TopLevelImplementing.aMethod") + @Override + public String aMethod() { + return "TopLevel.aMethod"; + } + } + + private static class TopLevelImplementingNoAnnot implements AnInterface { + @Override + public String aMethod() { + return "TopLevel.aMethod"; + } + } + + private static class Second extends TopLevel { + @Override + @Path("Second.aMethod") + public String aMethod() { + return "Second.aMethod"; + } + } + + private static class SecondImplement extends TopLevelImplementingNoAnnot { + @Override + @Path("SecondImplement.aMethod") + @RolesAllowed("test") + public String aMethod() { + return "SecondImplement.aMethod"; + } + } + + private static class SecondImplementNoAnnot extends TopLevelImplementingNoAnnot { + + } +} \ No newline at end of file diff --git a/jersey/pom.xml b/jersey/pom.xml index c5bfec9883b..f9bbac6f201 100644 --- a/jersey/pom.xml +++ b/jersey/pom.xml @@ -39,6 +39,7 @@ client server jsonp + common diff --git a/microprofile/tracing/pom.xml b/microprofile/tracing/pom.xml index a5a022e8da2..36a85f57d1a 100644 --- a/microprofile/tracing/pom.xml +++ b/microprofile/tracing/pom.xml @@ -51,6 +51,11 @@ helidon-microprofile-server ${project.version}
+ + io.helidon.jersey + helidon-jersey-common + ${project.version} + org.eclipse.microprofile.opentracing microprofile-opentracing-api diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java index 90ee586e4e3..7c6214fc6e0 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingFilter.java @@ -15,31 +15,28 @@ */ package io.helidon.microprofile.tracing; -import java.lang.reflect.Method; import java.net.URI; import java.util.Optional; import java.util.function.Function; +import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.annotation.Priority; import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.ConstrainedTo; -import javax.ws.rs.Path; import javax.ws.rs.RuntimeType; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Context; +import io.helidon.jersey.common.InvokedResource; import io.helidon.tracing.jersey.AbstractTracingFilter; import io.opentracing.Tracer; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.opentracing.Traced; -import org.glassfish.jersey.server.ExtendedUriInfo; -import org.glassfish.jersey.server.model.Invocable; -import org.glassfish.jersey.server.model.ResourceMethod; /** * Adds tracing of Jersey calls using a post-matching filter. @@ -49,6 +46,8 @@ @Priority(Integer.MIN_VALUE + 5) @ApplicationScoped public class MpTracingFilter extends AbstractTracingFilter { + private static final Pattern LOCALHOST_PATTERN = Pattern.compile("127.0.0.1", Pattern.LITERAL); + @Context private ResourceInfo resourceInfo; @@ -76,7 +75,8 @@ protected boolean tracingEnabled(ContainerRequestContext context) { if (skipPatternFunction.apply(addForwardSlash(context.getUriInfo().getPath()))) { return false; } - return findTraced(context) + return InvokedResource.create(context) + .findAnnotation(Traced.class) .map(Traced::value) .orElseGet(utils::tracingEnabled); } @@ -95,7 +95,8 @@ private String addForwardSlash(String path) { @Override protected String spanName(ContainerRequestContext context) { - return findTraced(context) + return InvokedResource.create(context) + .findAnnotation(Traced.class) .map(Traced::operationName) .filter(str -> !str.isEmpty()) .orElseGet(() -> utils.operationName(context)); @@ -123,7 +124,7 @@ protected String url(ContainerRequestContext requestContext) { if (hostHeader.contains("127.0.0.1")) { // TODO this is a bug in TCK tests, that expect localhost even though IP is sent - hostHeader = hostHeader.replace("127.0.0.1", "localhost"); + hostHeader = LOCALHOST_PATTERN.matcher(hostHeader).replaceAll(Matcher.quoteReplacement("localhost")); } // let us use host header instead of local interface @@ -136,62 +137,4 @@ protected String url(ContainerRequestContext requestContext) { return requestUri.toString(); } - - private Optional findTraced(ContainerRequestContext requestContext) { - Class definitionClass = getDefinitionClass(resourceInfo.getResourceClass()); - ExtendedUriInfo uriInfo = (ExtendedUriInfo) requestContext.getUriInfo(); - Method definitionMethod = getDefinitionMethod(requestContext, uriInfo); - - if (definitionMethod == null) { - return Optional.empty(); - } - - Traced annotation = definitionMethod.getAnnotation(Traced.class); - if (null != annotation) { - return Optional.of(annotation); - } - - return Optional.ofNullable(definitionClass.getAnnotation(Traced.class)); - } - - private Method getDefinitionMethod(ContainerRequestContext requestContext, ExtendedUriInfo uriInfo) { - ResourceMethod matchedResourceMethod = uriInfo.getMatchedResourceMethod(); - Invocable invocable = matchedResourceMethod.getInvocable(); - return invocable.getDefinitionMethod(); - } - - // taken from org.glassfish.jersey.server.model.internal.ModelHelper#getAnnotatedResourceClass - private Class getDefinitionClass(Class resourceClass) { - Class foundInterface = null; - - // traverse the class hierarchy to find the annotation - // According to specification, annotation in the super-classes must take precedence over annotation in the - // implemented interfaces - Class cls = resourceClass; - do { - if (cls.isAnnotationPresent(Path.class)) { - return cls; - } - - // if no annotation found on the class currently traversed, check for annotation in the interfaces on this - // level - if not already previously found - if (foundInterface == null) { - for (final Class i : cls.getInterfaces()) { - if (i.isAnnotationPresent(Path.class)) { - // store the interface reference in case no annotation will be found in the super-classes - foundInterface = i; - break; - } - } - } - - cls = cls.getSuperclass(); - } while (cls != null); - - if (foundInterface != null) { - return foundInterface; - } - - return resourceClass; - } } diff --git a/microprofile/tracing/src/main/java9/module-info.java b/microprofile/tracing/src/main/java9/module-info.java index f2706c05a1a..2a9c5bfb081 100644 --- a/microprofile/tracing/src/main/java9/module-info.java +++ b/microprofile/tracing/src/main/java9/module-info.java @@ -32,6 +32,7 @@ requires io.helidon.microprofile.server; requires io.helidon.common; requires io.helidon.webserver; + requires io.helidon.jersey.common; requires transitive io.helidon.tracing; requires transitive io.helidon.tracing.jersey; diff --git a/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TestTracerProvider.java b/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TestTracerProvider.java index 6b527853428..ec70bd81f3a 100644 --- a/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TestTracerProvider.java +++ b/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TestTracerProvider.java @@ -110,7 +110,31 @@ public Tracer build() { static class TestTracer implements Tracer { @Override public ScopeManager scopeManager() { - return null; + return new ScopeManager() { + private Scope active; + @Override + public Scope activate(Span span, boolean finishSpanOnClose) { + active = new Scope() { + @Override + public void close() { + if (finishSpanOnClose) { + span.finish(); + } + } + + @Override + public Span span() { + return span; + } + }; + return active; + } + + @Override + public Scope active() { + return active; + } + }; } @Override diff --git a/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingTest.java b/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingTest.java index d6cf99a6d70..374ca7e866c 100644 --- a/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingTest.java +++ b/microprofile/tracing/src/test/java/io/helidon/microprofile/tracing/TracingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,14 +88,14 @@ void testTracingPropagation() { // make sure that the operation is as expected (e.g. correctly propagated) String headerValue = (String) response.getHeaders().getFirst("X-FRONT-X-TEST-TRACER-OPERATION"); - assertThat(headerValue, is(ClientTracingFilter.SPAN_OPERATION_NAME)); + assertThat(headerValue, is("GET")); headerValue = (String) response.getHeaders().getFirst("X-FRONT-" + X_REQUEST_ID); assertThat(headerValue, is(xRequestId)); headerValue = (String) response.getHeaders().getFirst("X-FRONT-" + X_OT_SPAN_CONTEXT); assertThat(headerValue, is(xOtSpanContext)); headerValue = (String) response.getHeaders().getFirst("X-HELLO-X-TEST-TRACER-OPERATION"); - assertThat(headerValue, is(ClientTracingFilter.SPAN_OPERATION_NAME)); + assertThat(headerValue, is("GET")); headerValue = (String) response.getHeaders().getFirst("X-HELLO-" + X_REQUEST_ID); assertThat(headerValue, is(xRequestId)); headerValue = (String) response.getHeaders().getFirst("X-HELLO-" + X_OT_SPAN_CONTEXT); diff --git a/security/integration/jersey/pom.xml b/security/integration/jersey/pom.xml index 0f220b44107..c567bce0c13 100644 --- a/security/integration/jersey/pom.xml +++ b/security/integration/jersey/pom.xml @@ -60,6 +60,11 @@ helidon-security-providers-common ${project.version} + + io.helidon.jersey + helidon-jersey-common + ${project.version} + io.helidon.common helidon-common-service-loader diff --git a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilter.java b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilter.java index e2696f77c2d..b0d9e71feff 100644 --- a/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilter.java +++ b/security/integration/jersey/src/main/java/io/helidon/security/integration/jersey/SecurityFilter.java @@ -39,15 +39,14 @@ import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; -import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; import io.helidon.common.OptionalHelper; import io.helidon.common.serviceloader.HelidonServiceLoader; import io.helidon.config.Config; +import io.helidon.jersey.common.InvokedResource; import io.helidon.security.AuditEvent; import io.helidon.security.Security; import io.helidon.security.SecurityContext; @@ -84,12 +83,6 @@ public class SecurityFilter extends SecurityFilterCommon implements ContainerReq @Context private ServerConfig serverConfig; - @Context - private ResourceInfo resourceInfo; - - @Context - private UriInfo uriInfo; - @Context private SecurityContext securityContext; @@ -110,15 +103,11 @@ public SecurityFilter() { SecurityFilter(FeatureConfig featureConfig, Security security, ServerConfig serverConfig, - ResourceInfo resourceInfo, - UriInfo uriInfo, SecurityContext securityContext) { super(security, featureConfig); this.serverConfig = serverConfig; - this.resourceInfo = resourceInfo; - this.uriInfo = uriInfo; this.securityContext = securityContext; loadAnalyzers(); @@ -276,25 +265,23 @@ public void filter(ContainerRequestContext requestContext, ContainerResponseCont @Override protected FilterContext initRequestFiltering(ContainerRequestContext requestContext) { FilterContext context = new FilterContext(); - - if (!(requestContext.getUriInfo() instanceof ExtendedUriInfo)) { - throw new IllegalStateException("Could not get Extended Uri Info. Incompatible version of Jersey?"); - } - - ExtendedUriInfo uriInfo = (ExtendedUriInfo) requestContext.getUriInfo(); - - Method definitionMethod = getDefinitionMethod(requestContext, uriInfo); - - if (definitionMethod == null) { - // this will end in 404, just let it on - context.setShouldFinish(true); - return context; - } - - context.setMethodSecurity(getMethodSecurity(definitionMethod, uriInfo)); - context.setResourceName(definitionMethod.getDeclaringClass().getSimpleName()); - - return configureContext(context, requestContext, uriInfo); + InvokedResource invokedResource = InvokedResource.create(requestContext); + + return invokedResource + .definitionMethod() + .map(definitionMethod -> { + context.setMethodSecurity(getMethodSecurity(invokedResource, + definitionMethod, + (ExtendedUriInfo) requestContext.getUriInfo())); + context.setResourceName(definitionMethod.getDeclaringClass().getSimpleName()); + + return configureContext(context, requestContext, requestContext.getUriInfo()); + }) + .orElseGet(() -> { + // this will end in 404, just let it on + context.setShouldFinish(true); + return context; + }); } @Override @@ -343,7 +330,9 @@ private SecurityDefinition securityForClass(Class theClass, SecurityDefinitio return definition; } - private SecurityDefinition getMethodSecurity(Method definitionMethod, ExtendedUriInfo uriInfo) { + private SecurityDefinition getMethodSecurity(InvokedResource invokedResource, + Method definitionMethod, + ExtendedUriInfo uriInfo) { // Check cache // Jersey model 'definition method' is the method that contains JAX-RS/Jersey annotations. JAX-RS does not support @@ -351,7 +340,8 @@ private SecurityDefinition getMethodSecurity(Method definitionMethod, ExtendedUr // and abstract classes implemented by the definition method. // Jersey model does not have a 'definition class', so we have to find it from a handler class - Class definitionClass = getDefinitionClass(resourceInfo.getResourceClass()); + Class definitionClass = invokedResource.definitionClass() + .orElseThrow(() -> new SecurityException("Got definition method, cannot get definition class")); if (definitionClass.getAnnotation(Path.class) == null) { // this is a sub-resource @@ -484,50 +474,6 @@ private void addCustomAnnotations(Map, List getDefinitionClass(Class resourceClass) { - Class foundInterface = null; - - // traverse the class hierarchy to find the annotation - // According to specification, annotation in the super-classes must take precedence over annotation in the - // implemented interfaces - Class cls = resourceClass; - do { - if (cls.isAnnotationPresent(Path.class)) { - return cls; - } - - // if no annotation found on the class currently traversed, check for annotation in the interfaces on this - // level - if not already previously found - if (foundInterface == null) { - for (final Class i : cls.getInterfaces()) { - if (i.isAnnotationPresent(Path.class)) { - // store the interface reference in case no annotation will be found in the super-classes - foundInterface = i; - break; - } - } - } - - cls = cls.getSuperclass(); - } while (cls != null); - - if (foundInterface != null) { - return foundInterface; - } - - return resourceClass; - } - private Application getOriginalApplication() { // Unfortunately the following logic is very "implementation aware". We need the original instance of // javax.ws.rs.core.Application to get the @Authenticated annotation instance if present. diff --git a/security/integration/jersey/src/main/java9/module-info.java b/security/integration/jersey/src/main/java9/module-info.java index 92351fd1b34..5f67a6b407a 100644 --- a/security/integration/jersey/src/main/java9/module-info.java +++ b/security/integration/jersey/src/main/java9/module-info.java @@ -29,6 +29,7 @@ requires transitive java.ws.rs; requires io.helidon.common.context; + requires io.helidon.jersey.common; requires io.helidon.security.integration.common; requires jersey.common; requires jersey.server; diff --git a/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/OptionalSecurityTest.java b/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/OptionalSecurityTest.java index a0af322cc62..14382f30bc5 100644 --- a/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/OptionalSecurityTest.java +++ b/security/integration/jersey/src/test/java/io/helidon/security/integration/jersey/OptionalSecurityTest.java @@ -23,10 +23,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; -import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Application; -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.UriInfo; import io.helidon.common.CollectionsHelper; import io.helidon.security.AuthenticationResponse; @@ -45,7 +42,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -65,8 +61,6 @@ class OptionalSecurityTest { private static Security security; private static FeatureConfig featureConfig; private static ResourceConfig serverConfig; - private static ResourceInfo resourceInfo; - private static UriInfo uriInfo; private static SecurityTracing tracing; @BeforeAll @@ -82,12 +76,6 @@ static void init() { serverConfig = ResourceConfig.forApplication(getApplication()); - resourceInfo = mock(ResourceInfo.class); - doReturn(TheResource.class).when(resourceInfo).getResourceClass(); - - uriInfo = mock(UriInfo.class); - when(uriInfo.getQueryParameters()).thenReturn(new MultivaluedHashMap<>()); - AuthenticationResponse atr = AuthenticationResponse.builder() .status(SecurityResponse.SecurityStatus.FAILURE_FINISH) .statusCode(301) @@ -113,8 +101,6 @@ void testOptional() { secuFilter = new SecurityFilter(featureConfig, security, serverConfig, - resourceInfo, - uriInfo, secuContext); /* @@ -140,8 +126,6 @@ void testNotOptional() { secuFilter = new SecurityFilter(featureConfig, security, serverConfig, - resourceInfo, - uriInfo, secuContext); /* * The actual tested method diff --git a/tracing/jaeger/pom.xml b/tracing/jaeger/pom.xml index b4bcda9ca50..a33ed13d545 100644 --- a/tracing/jaeger/pom.xml +++ b/tracing/jaeger/pom.xml @@ -18,14 +18,19 @@ + 4.0.0 - helidon-tracing-project io.helidon.tracing + helidon-tracing-project 1.1.3-SNAPSHOT - 4.0.0 helidon-tracing-jaeger + Helidon Tracing Jaeger + + + Integration with Jaeger tracing + diff --git a/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java b/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java index 279f497a7cd..bd96128ff2d 100644 --- a/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java +++ b/tracing/jersey-client/src/main/java/io/helidon/tracing/jersey/client/ClientTracingFilter.java @@ -75,7 +75,8 @@ * Inbound HTTP headers are resolved from JAX-RS server. * *

- * For each client call, a new {@link Span} with operation name {@value #SPAN_OPERATION_NAME} is created based + * For each client call, a new {@link Span} with operation name generated from HTTP method and resource method + * and class is created based * on the resolved {@link Tracer} and an optional parent {@link Span}. The span information is also * propagated to the outbound request using HTTP headers injected by tracer. *

@@ -120,9 +121,9 @@ public class ClientTracingFilter implements ClientRequestFilter, ClientResponseF */ public static final String CURRENT_SPAN_CONTEXT_PROPERTY_NAME = "io.helidon.tracing.span-context"; /** - * Operation name of a span created for outbound calls. + * Name of the configuration of a span created for outbound calls. */ - public static final String SPAN_OPERATION_NAME = "jersey-client-call"; + private static final String SPAN_OPERATION_NAME = "jersey-client-call"; /* * Known headers to be propagated from inbound request */ diff --git a/tracing/jersey/pom.xml b/tracing/jersey/pom.xml index b0fecea7aca..42bdcc1db9c 100644 --- a/tracing/jersey/pom.xml +++ b/tracing/jersey/pom.xml @@ -43,6 +43,11 @@ helidon-tracing-jersey-client ${project.version} + + io.helidon.jersey + helidon-jersey-common + ${project.version} + io.helidon.common helidon-common diff --git a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java index 5abceff701f..45d986ac1f0 100644 --- a/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java +++ b/tracing/jersey/src/main/java/io/helidon/tracing/jersey/TracingHelper.java @@ -16,19 +16,18 @@ package io.helidon.tracing.jersey; import java.lang.reflect.Method; +import java.util.Optional; import java.util.function.Function; import javax.ws.rs.Path; import javax.ws.rs.container.ContainerRequestContext; -import org.glassfish.jersey.server.ExtendedUriInfo; -import org.glassfish.jersey.server.model.Invocable; -import org.glassfish.jersey.server.model.ResourceMethod; +import io.helidon.jersey.common.InvokedResource; /** * Utilities for tracing in helidon. */ -public class TracingHelper { +public final class TracingHelper { private final Function nameFunction; private TracingHelper(Function nameFunction) { @@ -63,29 +62,35 @@ public static TracingHelper create(Function nam * @return name of span to use */ public static String httpPathMethodName(ContainerRequestContext requestContext) { - Method m = getDefinitionMethod(requestContext); - Path methodPath = m.getAnnotation(Path.class); - Path resourcePath = m.getDeclaringClass().getAnnotation(Path.class); + InvokedResource invokedResource = InvokedResource.create(requestContext); + Optional method = invokedResource.definitionMethod(); + + if (!method.isPresent()) { + return requestContext.getMethod().toUpperCase() + ":" + requestContext.getUriInfo().getPath(); + } StringBuilder fullPath = new StringBuilder(); fullPath.append(requestContext.getMethod().toUpperCase()); fullPath.append(":"); - if (null != resourcePath) { - String resourcePathS = resourcePath.value(); - if (!resourcePathS.startsWith("/")) { - fullPath.append("/"); - } - fullPath.append(resourcePath.value()); - } - if (null != methodPath) { - String methodPathS = methodPath.value(); - - if ((fullPath.length() != 0) && (fullPath.charAt(fullPath.length() - 1) != '/') && !methodPathS.startsWith("/")) { - fullPath.append("/"); - } - fullPath.append(methodPath.value()); - } + Optional classPathAnnotation = invokedResource.findClassAnnotation(Path.class); + classPathAnnotation.map(Path::value) + .ifPresent(resourcePath -> { + if (!resourcePath.startsWith("/")) { + fullPath.append("/"); + } + fullPath.append(resourcePath); + }); + + Optional methodPathAnnotation = invokedResource.findMethodAnnotation(Path.class); + methodPathAnnotation.map(Path::value) + .ifPresent(methodPath -> { + if ((fullPath.length() != 0) && (fullPath.charAt(fullPath.length() - 1) != '/') + && !methodPath.startsWith("/")) { + fullPath.append("/"); + } + fullPath.append(methodPath); + }); return fullPath.toString(); } @@ -97,25 +102,12 @@ public static String httpPathMethodName(ContainerRequestContext requestContext) * @return name of span to use */ public static String classMethodName(ContainerRequestContext requestContext) { - Method m = getDefinitionMethod(requestContext); - - return requestContext.getMethod() - + ":" + m.getDeclaringClass().getName() - + "." + m.getName(); - } - - /** - * The term 'definition method' used by the Jersey model means the method that contains JAX-RS/Jersey annotations. - */ - static Method getDefinitionMethod(ContainerRequestContext requestContext) { - if (!(requestContext.getUriInfo() instanceof ExtendedUriInfo)) { - throw new IllegalStateException("Could not get Extended Uri Info. Incompatible version of Jersey?"); - } - - ExtendedUriInfo uriInfo = (ExtendedUriInfo) requestContext.getUriInfo(); - ResourceMethod matchedResourceMethod = uriInfo.getMatchedResourceMethod(); - Invocable invocable = matchedResourceMethod.getInvocable(); - return invocable.getDefinitionMethod(); + return InvokedResource.create(requestContext) + .definitionMethod() + .map(m -> requestContext.getMethod().toUpperCase() + + ":" + m.getDeclaringClass().getName() + + "." + m.getName()) + .orElseGet(() -> requestContext.getMethod().toUpperCase() + ":404"); } /** diff --git a/tracing/jersey/src/main/java9/module-info.java b/tracing/jersey/src/main/java9/module-info.java index 0fe01946424..5be95981c75 100644 --- a/tracing/jersey/src/main/java9/module-info.java +++ b/tracing/jersey/src/main/java9/module-info.java @@ -26,6 +26,7 @@ requires opentracing.api; requires io.helidon.common; requires io.helidon.common.context; + requires io.helidon.jersey.common; requires io.helidon.webserver; requires transitive io.helidon.tracing.jersey.client; From 245163f8cacbac21292dc55d77fea6ef334ea725 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 9 Jul 2019 17:17:34 +0200 Subject: [PATCH 4/9] Checkstyle fix Signed-off-by: Tomas Langer --- .../helidon/jersey/common/InvokedResourceImpl.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java b/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java index ce8ed2fef3c..ca9699bbfde 100644 --- a/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java +++ b/jersey/common/src/main/java/io/helidon/jersey/common/InvokedResourceImpl.java @@ -194,8 +194,8 @@ public boolean equals(Object o) { return false; } ClassAnnotationKey that = (ClassAnnotationKey) o; - return annotationClass.equals(that.annotationClass) && - handlingClass.equals(that.handlingClass); + return annotationClass.equals(that.annotationClass) + && handlingClass.equals(that.handlingClass); } @Override @@ -230,10 +230,10 @@ public boolean equals(Object o) { return false; } MethodAnnotationKey that = (MethodAnnotationKey) o; - return annotationClass.equals(that.annotationClass) && - definitionClass.equals(that.definitionClass) && - handlingClass.equals(that.handlingClass) && - handlingMethod.equals(that.handlingMethod); + return annotationClass.equals(that.annotationClass) + && definitionClass.equals(that.definitionClass) + && handlingClass.equals(that.handlingClass) + && handlingMethod.equals(that.handlingMethod); } @Override From 6546403a3bcca774130915959d236367ad93febf Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Jul 2019 13:56:22 +0200 Subject: [PATCH 5/9] Removed redundant null check Signed-off-by: Tomas Langer --- .../microprofile/tracing/MpTracingRestClientFilter.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java index ffbdf967cee..851f3676695 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java @@ -43,10 +43,6 @@ public void filter(ClientRequestContext requestContext) { Traced traced = invokedMethod.getAnnotation(Traced.class); - if (null == traced) { - return; - } - boolean enabled; String opName; From 79e162eb1465d428ba18ed446b36dd2b236e9145 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Jul 2019 16:59:54 +0200 Subject: [PATCH 6/9] Reworked the redundant null check, now TCK passes. Signed-off-by: Tomas Langer --- .../tracing/MpTracingRestClientFilter.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java index 851f3676695..a104a7934b9 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientFilter.java @@ -42,17 +42,15 @@ public void filter(ClientRequestContext requestContext) { } Traced traced = invokedMethod.getAnnotation(Traced.class); + if (null == traced) { + return; + } boolean enabled; String opName; - if (null != traced) { - enabled = traced.value(); - opName = traced.operationName(); - } else { - enabled = true; - opName = ""; - } + enabled = traced.value(); + opName = traced.operationName(); if (!opName.isEmpty()) { requestContext.setProperty(ClientTracingFilter.SPAN_NAME_PROPERTY_NAME, opName); From 68c54aaed0ca2a93b814129d8107bf393d23a8dc Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Jul 2019 20:17:09 +0200 Subject: [PATCH 7/9] Added TCK for rest client. Configuring the executor service for rest client correctly (removed from Tracing) Signed-off-by: Tomas Langer --- microprofile/rest-client/pom.xml | 18 +++- .../restclient/MpRestClientListener.java | 53 +++++++++++ .../microprofile/restclient/package-info.java | 19 ++++ .../src/main/java9/module-info.java | 5 ++ ...profile.rest.client.spi.RestClientListener | 17 ++++ .../HelidonContainerConfiguration.java | 9 ++ .../HelidonDeployableContainer.java | 7 +- .../microprofile/arquillian/ServerRunner.java | 1 + microprofile/tests/tck/pom.xml | 1 + .../tests/tck/tck-rest-client/pom.xml | 88 +++++++++++++++++++ .../internal/StringMessageProvider.java | 77 ++++++++++++++++ .../src/test/resources/arquillian.xml | 35 ++++++++ .../tests/tck/tck-rest-client/tck-suite.xml | 25 ++++++ .../tracing/MpTracingRestClientListener.java | 2 +- 14 files changed, 351 insertions(+), 6 deletions(-) create mode 100644 microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/MpRestClientListener.java create mode 100644 microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/package-info.java create mode 100644 microprofile/rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener create mode 100644 microprofile/tests/tck/tck-rest-client/pom.xml create mode 100644 microprofile/tests/tck/tck-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java create mode 100644 microprofile/tests/tck/tck-rest-client/src/test/resources/arquillian.xml create mode 100644 microprofile/tests/tck/tck-rest-client/tck-suite.xml diff --git a/microprofile/rest-client/pom.xml b/microprofile/rest-client/pom.xml index f4ecd8b6535..ab9da0d348c 100644 --- a/microprofile/rest-client/pom.xml +++ b/microprofile/rest-client/pom.xml @@ -28,14 +28,24 @@ io.helidon.microprofile.rest-client helidon-microprofile-rest-client + Helidon Microprofile Rest Client - - - true - + + Extension for Jersey implementation of MicroProfile Rest Client + + + io.helidon.common + helidon-common-context + ${project.version} + + + io.helidon.jersey + helidon-jersey-client + ${project.version} + org.glassfish.jersey.ext.microprofile jersey-mp-rest-client diff --git a/microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/MpRestClientListener.java b/microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/MpRestClientListener.java new file mode 100644 index 00000000000..d8967ce9240 --- /dev/null +++ b/microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/MpRestClientListener.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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 io.helidon.microprofile.restclient; + +import java.lang.reflect.Field; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +import io.helidon.common.context.Contexts; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.spi.RestClientListener; + +/** + * A client listener that wraps executor service with + * {@link io.helidon.common.context.Contexts#wrap(java.util.concurrent.ExecutorService)}. + */ +public class MpRestClientListener implements RestClientListener { + private static final Logger LOGGER = Logger.getLogger(MpRestClientListener.class.getName()); + + @SuppressWarnings("unchecked") + @Override + public void onNewClient(Class aClass, RestClientBuilder restClientBuilder) { + // we know there is an executor service we need to wrap, so let us do it + // the class RestClientBuilderImpl has a field + // private Supplier executorService; + // we replace the value with a wrapper + try { + Field execServiceField = restClientBuilder.getClass().getDeclaredField("executorService"); + execServiceField.setAccessible(true); + Supplier existingSupplier = (Supplier) execServiceField.get(restClientBuilder); + Supplier newSupplier = () -> Contexts.wrap(existingSupplier.get()); + execServiceField.set(restClientBuilder, newSupplier); + } catch (Exception e) { + LOGGER.log(Level.FINE, "Failed to replace executor service for a REST Client: " + aClass, e); + } + } +} diff --git a/microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/package-info.java b/microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/package-info.java new file mode 100644 index 00000000000..f27a910bc4a --- /dev/null +++ b/microprofile/rest-client/src/main/java/io/helidon/microprofile/restclient/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ +/** + * Helidon specific extension to the rest client implementation from Jersey. + */ +package io.helidon.microprofile.restclient; diff --git a/microprofile/rest-client/src/main/java9/module-info.java b/microprofile/rest-client/src/main/java9/module-info.java index 4fe8d39eecc..6fbd22decc3 100644 --- a/microprofile/rest-client/src/main/java9/module-info.java +++ b/microprofile/rest-client/src/main/java9/module-info.java @@ -18,6 +18,11 @@ * MP Rest clienr. */ module io.helidon.microprofile.restclient { + requires java.logging; requires transitive microprofile.rest.client.api; + requires io.helidon.common.context; requires jersey.mp.rest.client; + + provides org.eclipse.microprofile.rest.client.spi.RestClientListener + with io.helidon.microprofile.restclient.MpRestClientListener; } diff --git a/microprofile/rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener b/microprofile/rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener new file mode 100644 index 00000000000..024644e3e9f --- /dev/null +++ b/microprofile/rest-client/src/main/resources/META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener @@ -0,0 +1,17 @@ +# +# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. +# + +io.helidon.microprofile.restclient.MpRestClientListener \ No newline at end of file diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java index 25d14320615..b5f7dcb1a04 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java @@ -40,6 +40,7 @@ public class HelidonContainerConfiguration implements ContainerConfiguration { private boolean deleteTmp = true; private boolean addResourcesToApps = false; private boolean replaceConfigSourcesWithMp = false; + private boolean useRelativePath = false; public String getApp() { return appClassName; @@ -73,6 +74,14 @@ public void setDeleteTmp(boolean b) { this.deleteTmp = b; } + public boolean getUseRelativePath() { + return useRelativePath; + } + + public void setUseRelativePath(boolean b) { + this.useRelativePath = b; + } + public boolean getAddResourcesToApps() { return addResourcesToApps; } diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java index 7f3c748619d..cb4ab552805 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java @@ -23,6 +23,7 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.security.PrivilegedAction; import java.util.ArrayList; @@ -123,7 +124,11 @@ public ProtocolMetaData deploy(Archive archive) throws DeploymentException { try { // Create the temporary deployment directory. - context.deployDir = Files.createTempDirectory("helidon-arquillian-test"); + if (containerConfig.getUseRelativePath()) { + context.deployDir = Paths.get("target/helidon-arquillian-test"); + } else { + context.deployDir = Files.createTempDirectory("helidon-arquillian-test"); + } LOGGER.info("Running Arquillian tests in directory: " + context.deployDir.toAbsolutePath()); // Copy the archive into deployDir. Save off the class names for all classes included in the diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java index 9a088843213..baeb7ada1be 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/ServerRunner.java @@ -76,6 +76,7 @@ void stop() { } } + @SuppressWarnings("unchecked") private void handleClasses(ClassLoader classLoader, Set classNames, Server.Builder builder, diff --git a/microprofile/tests/tck/pom.xml b/microprofile/tests/tck/pom.xml index 4dd87240274..9194a3ab069 100644 --- a/microprofile/tests/tck/pom.xml +++ b/microprofile/tests/tck/pom.xml @@ -38,6 +38,7 @@ tck-jwt-auth tck-openapi tck-opentracing + tck-rest-client diff --git a/microprofile/tests/tck/tck-rest-client/pom.xml b/microprofile/tests/tck/tck-rest-client/pom.xml new file mode 100644 index 00000000000..57f464a0ce8 --- /dev/null +++ b/microprofile/tests/tck/tck-rest-client/pom.xml @@ -0,0 +1,88 @@ + + + + + + tck-project + io.helidon.microprofile.tests + 1.1.3-SNAPSHOT + + 4.0.0 + + tck-rest-client + Helidon Microprofile Tests TCK REST Client + + + + io.helidon.microprofile.tests + helidon-arquillian + ${project.version} + test + + + + org.glassfish.jersey.media + jersey-media-json-binding + + + + + org.eclipse.microprofile.rest.client + microprofile-rest-client-tck + 1.2.1 + test + + + javax.xml.bind + jaxb-api + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + tck-suite.xml + + + + + uk.co.deliverymind + wiremock-maven-plugin + 2.7.0 + + + test-compile + + run + + +

target/classes + --port=8765 + + + + + + +
\ No newline at end of file diff --git a/microprofile/tests/tck/tck-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java b/microprofile/tests/tck/tck-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java new file mode 100644 index 00000000000..ec18c547eba --- /dev/null +++ b/microprofile/tests/tck/tck-rest-client/src/test/java/org/glassfish/jersey/message/internal/StringMessageProvider.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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.glassfish.jersey.message.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.inject.Singleton; +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; + +/* + * This class was copied from Jersey Common 2.26 to workaround a TCK issue - the original class works for any media type + * this only works for text + */ + +@Produces +@Consumes +@Singleton +final class StringMessageProvider extends AbstractMessageReaderWriterProvider { + + @Override + public boolean isReadable(Class type, Type genericType, Annotation annotations[], MediaType mediaType) { + return type == String.class; + } + + @Override + public String readFrom( + Class type, + Type genericType, + Annotation annotations[], + MediaType mediaType, + MultivaluedMap httpHeaders, + InputStream entityStream) throws IOException { + return readFromAsString(entityStream, mediaType); + } + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation annotations[], MediaType mediaType) { + return type == String.class; + } + + @Override + public long getSize(String s, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return s.length(); + } + + @Override + public void writeTo( + String t, + Class type, + Type genericType, + Annotation annotations[], + MediaType mediaType, + MultivaluedMap httpHeaders, + OutputStream entityStream) throws IOException { + writeToAsString(t, entityStream, mediaType); + } +} diff --git a/microprofile/tests/tck/tck-rest-client/src/test/resources/arquillian.xml b/microprofile/tests/tck/tck-rest-client/src/test/resources/arquillian.xml new file mode 100644 index 00000000000..0a7e07ca5c8 --- /dev/null +++ b/microprofile/tests/tck/tck-rest-client/src/test/resources/arquillian.xml @@ -0,0 +1,35 @@ + + + + + + target/deployments + 8080 + + + + + true + true + false + + + \ No newline at end of file diff --git a/microprofile/tests/tck/tck-rest-client/tck-suite.xml b/microprofile/tests/tck/tck-rest-client/tck-suite.xml new file mode 100644 index 00000000000..bf6c7d0eff8 --- /dev/null +++ b/microprofile/tests/tck/tck-rest-client/tck-suite.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java index cfd2833a7fc..9438a8ed9ab 100644 --- a/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java +++ b/microprofile/tracing/src/main/java/io/helidon/microprofile/tracing/MpTracingRestClientListener.java @@ -49,7 +49,7 @@ public void onNewClient(Class serviceInterface, RestClientBuilder builder) { builder.register(REST_CLIENT_FILTER, Priorities.AUTHENTICATION - 300); builder.register(FILTER, Priorities.AUTHENTICATION - 250); - builder.executorService(MpTracingClientRegistrar.EXECUTOR_SERVICE.get()); + if (!opName.isEmpty()) { builder.property(ClientTracingFilter.SPAN_NAME_PROPERTY_NAME, opName); } From e69ff9a14b0c40b22970772c64f8dafb9fcd27b8 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Jul 2019 20:19:14 +0200 Subject: [PATCH 8/9] Using dependency management for rest client TCK, copyright Signed-off-by: Tomas Langer --- .../arquillian/HelidonContainerConfiguration.java | 2 +- microprofile/tests/tck/tck-rest-client/pom.xml | 1 - pom.xml | 5 +++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java index b5f7dcb1a04..1f8466065fd 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/tests/tck/tck-rest-client/pom.xml b/microprofile/tests/tck/tck-rest-client/pom.xml index 57f464a0ce8..c8210501305 100644 --- a/microprofile/tests/tck/tck-rest-client/pom.xml +++ b/microprofile/tests/tck/tck-rest-client/pom.xml @@ -45,7 +45,6 @@ org.eclipse.microprofile.rest.client microprofile-rest-client-tck - 1.2.1 test diff --git a/pom.xml b/pom.xml index 1d7b7d03411..2a9f27504e3 100644 --- a/pom.xml +++ b/pom.xml @@ -1068,6 +1068,11 @@ microprofile-rest-client-api ${version.lib.microprofile-rest-client} + + org.eclipse.microprofile.rest.client + microprofile-rest-client-tck + ${version.lib.microprofile-rest-client} + com.netflix.hystrix hystrix-core From a6c08ff65fca614443e026aa7a015efb8240016c Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Jul 2019 21:40:41 +0200 Subject: [PATCH 9/9] Fixed order of isAssignableFrom Signed-off-by: Tomas Langer --- .../microprofile/opentracing/tck/UrlResourceProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java index b407e79cd93..32f4b082536 100644 --- a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java +++ b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/UrlResourceProvider.java @@ -39,6 +39,6 @@ public Object lookup(ArquillianResource arquillianResource, Annotation... annota @Override public boolean canProvide(Class type) { - return type.isAssignableFrom(URL.class); + return URL.class.isAssignableFrom(type); } }