diff --git a/.github/workflows/plugins-test.1.yaml b/.github/workflows/plugins-test.1.yaml index 0674b0b28f..ad17e7fefa 100644 --- a/.github/workflows/plugins-test.1.yaml +++ b/.github/workflows/plugins-test.1.yaml @@ -77,6 +77,7 @@ jobs: - mssql-jtds-scenario - mssql-jdbc-scenario - mybatis-3.x-scenario + - resteasy-4.x-scenario steps: - uses: actions/checkout@v2 with: diff --git a/CHANGES.md b/CHANGES.md index 12e4f349b9..628c5c7eff 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Release Notes. * Fix `Shenyu plugin`'s NPE in reading read trace ID when IgnoredTracerContext is used in the context. * Update witness class in elasticsearch-6.x-plugin, avoid throw NPE. * Fix `onHalfClose` using span operation name `/Request/onComplete` instead of the worng name `/Request/onHalfClose`. +* Add plugin to support for RESTeasy 4.x. #### Documentation diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml index dacb1d922b..5d6f8fd761 100644 --- a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/pom.xml @@ -27,6 +27,7 @@ resteasy-plugin resteasy-server-3.x-plugin + resteasy-server-4.x-plugin pom diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/pom.xml new file mode 100644 index 0000000000..eaf4f65ecd --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/pom.xml @@ -0,0 +1,53 @@ + + + + 4.0.0 + + resteasy-plugin + org.apache.skywalking + 8.12.0-SNAPSHOT + + + resteasy-server-4.x-plugin + jar + + apm-resteasy-server-4.x-plugin + http://maven.apache.org + + + 4.7.6.Final + 3.15.3.Final + + + + + org.jboss.resteasy + resteasy-core + ${resteasy.version} + provided + + + org.jboss.resteasy + resteasy-jaxrs + ${resteasy-jaxrs.version} + provided + + + \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherExceptionInterceptor.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherExceptionInterceptor.java new file mode 100644 index 0000000000..57e0c6b815 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherExceptionInterceptor.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.resteasy.v4.server; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.jboss.resteasy.spi.HttpRequest; + +import java.lang.reflect.Method; + +public class SynchronousDispatcherExceptionInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + if (ContextManager.isActive() && !((HttpRequest) allArguments[0]).getAsyncContext().isSuspended()) { + ContextManager.activeSpan().log((Throwable) allArguments[2]); + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherInterceptor.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherInterceptor.java new file mode 100644 index 0000000000..231ffb8851 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherInterceptor.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.resteasy.v4.server; + +import org.apache.skywalking.apm.agent.core.context.CarrierItem; +import org.apache.skywalking.apm.agent.core.context.ContextCarrier; +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.tag.Tags; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; + +public class SynchronousDispatcherInterceptor implements InstanceMethodsAroundInterceptor { + private static final Logger LOG = LoggerFactory.getLogger(SynchronousDispatcherInterceptor.class); + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + MethodInterceptResult result) throws Throwable { + HttpRequest request = (HttpRequest) allArguments[0]; + + ContextCarrier contextCarrier = new ContextCarrier(); + CarrierItem next = contextCarrier.items(); + while (next.hasNext()) { + next = next.next(); + next.setHeadValue(request.getHttpHeaders().getHeaderString(next.getHeadKey())); + } + + String operationName = request.getHttpMethod() + ":" + request.getUri().getPath(); + AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier); + span.tag(Tags.URL, toPath(request.getUri().getRequestUri().toString())); + span.tag(Tags.HTTP.METHOD, request.getHttpMethod()); + span.setComponent(ComponentsDefine.RESTEASY); + SpanLayer.asHttp(span); + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, + Object ret) throws Throwable { + HttpResponse response = (HttpResponse) allArguments[1]; + AbstractSpan span = ContextManager.activeSpan(); + if (response.getStatus() >= 400) { + span.errorOccurred(); + } + Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.getStatus()); + ContextManager.stopSpan(); + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, + Class[] argumentsTypes, Throwable t) { + ContextManager.activeSpan().log(t); + } + + private static String toPath(String uri) { + int index = uri.indexOf("?"); + if (index > -1) { + return uri.substring(0, index); + } else { + return uri; + } + } +} diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/define/SynchronousDispatcherInstrumentation.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/define/SynchronousDispatcherInstrumentation.java new file mode 100644 index 0000000000..245994dc69 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/define/SynchronousDispatcherInstrumentation.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.resteasy.v4.server.define; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine; +import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; +import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +public class SynchronousDispatcherInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { + + private static final String ENHANCE_CLASS = "org.jboss.resteasy.core.SynchronousDispatcher"; + + private static final String INVOKE_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.resteasy.v4.server.SynchronousDispatcherInterceptor"; + private static final String INVOKE_EXCEPTION_INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.resteasy.v4.server.SynchronousDispatcherExceptionInterceptor"; + + @Override + public ConstructorInterceptPoint[] getConstructorsInterceptPoints() { + return null; + } + + @Override + public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() { + return new InstanceMethodsInterceptPoint[] { + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("invoke").and(takesArguments(3)); + } + + @Override + public String getMethodsInterceptor() { + return INVOKE_INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("writeException").and(takesArguments(3)); + } + + @Override + public String getMethodsInterceptor() { + return INVOKE_EXCEPTION_INTERCEPT_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + } + }; + } + + @Override + protected ClassMatch enhanceClass() { + return NameMatch.byName(ENHANCE_CLASS); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/resources/skywalking-plugin.def new file mode 100644 index 0000000000..dd7cfe0aac --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/main/resources/skywalking-plugin.def @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resteasy-server-4.x=org.apache.skywalking.apm.plugin.resteasy.v4.server.define.SynchronousDispatcherInstrumentation \ No newline at end of file diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/AssertTools.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/AssertTools.java new file mode 100644 index 0000000000..991ead2a12 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/AssertTools.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.resteasy.v4.server; + +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegmentRef; +import org.apache.skywalking.apm.agent.test.helper.SegmentRefHelper; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.network.trace.component.ComponentsDefine; + +import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class AssertTools { + + static void assertTraceSegmentRef(TraceSegmentRef ref) { + assertThat(SegmentRefHelper.getParentServiceInstance(ref), is("instance")); + assertThat(SegmentRefHelper.getSpanId(ref), is(3)); + assertThat(SegmentRefHelper.getTraceSegmentId(ref).toString(), is("3.4.5")); + } + + static void assertHttpSpan(AbstractTracingSpan span) { + assertThat(span.getOperationName(), is("GET:/test/testRequestURL")); + assertComponent(span, ComponentsDefine.RESTEASY); + SpanAssert.assertTag(span, 0, "http://localhost:8080/test/testRequestURL"); + assertThat(span.isEntry(), is(true)); + SpanAssert.assertLayer(span, SpanLayer.HTTP); + } +} diff --git a/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherInterceptorTest.java new file mode 100644 index 0000000000..df54c505e0 --- /dev/null +++ b/apm-sniffer/apm-sdk-plugin/resteasy-plugin/resteasy-server-4.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/resteasy/v4/server/SynchronousDispatcherInterceptorTest.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.apm.plugin.resteasy.v4.server; + +import org.apache.skywalking.apm.agent.core.context.SW8CarrierItem; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan; +import org.apache.skywalking.apm.agent.core.context.trace.LogDataEntity; +import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.apache.skywalking.apm.agent.test.helper.SegmentHelper; +import org.apache.skywalking.apm.agent.test.helper.SpanHelper; +import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule; +import org.apache.skywalking.apm.agent.test.tools.SegmentStorage; +import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint; +import org.apache.skywalking.apm.agent.test.tools.SpanAssert; +import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner; +import org.jboss.resteasy.core.ResourceInvoker; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.jboss.resteasy.specimpl.ResteasyHttpHeaders; +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.jboss.resteasy.spi.ResteasyAsynchronousContext; +import org.jboss.resteasy.spi.ResteasyUriInfo; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(TracingSegmentRunner.class) +public class SynchronousDispatcherInterceptorTest { + + private SynchronousDispatcherInterceptor synchronousDispatcherInterceptor; + private SynchronousDispatcherExceptionInterceptor exceptionInterceptor; + + @SegmentStoragePoint + private SegmentStorage segmentStorage; + + @Rule + public AgentServiceRule serviceRule = new AgentServiceRule(); + + @Mock + HttpRequest request; + + @Mock + HttpResponse response; + + @Mock + ResourceInvoker resourceInvoker; + + @Mock + private MethodInterceptResult methodInterceptResult; + + @Mock + private ResteasyAsynchronousContext resteasyAsynchronousContext; + + @Mock + EnhancedInstance enhancedInstance; + + private Object[] arguments; + private Class[] argumentType; + + private Object[] exceptionArguments; + private Class[] exceptionArgumentType; + + @Before + public void setup() throws URISyntaxException { + synchronousDispatcherInterceptor = new SynchronousDispatcherInterceptor(); + exceptionInterceptor = new SynchronousDispatcherExceptionInterceptor(); + when(request.getUri()).thenReturn(new ResteasyUriInfo(new URI("http://localhost:8080/test/testRequestURL"))); + when(request.getHttpHeaders()).thenReturn(new ResteasyHttpHeaders(new MultivaluedMapImpl())); + when(request.getHttpMethod()).thenReturn("GET"); + when(response.getStatus()).thenReturn(200); + when(request.getAsyncContext()).thenReturn(resteasyAsynchronousContext); + when(request.getAsyncContext().isSuspended()).thenReturn(false); + arguments = new Object[] { + request, + response, + resourceInvoker + }; + argumentType = new Class[] { + request.getClass(), + response.getClass(), + resourceInvoker.getClass() + }; + + exceptionArguments = new Object[] { + request, + response, + new RuntimeException() + }; + exceptionArgumentType = new Class[] { + request.getClass(), + response.getClass(), + new RuntimeException().getClass() + }; + } + + @Test + public void testWithoutSerializedContextData() throws Throwable { + synchronousDispatcherInterceptor.beforeMethod(enhancedInstance, null, arguments, argumentType, methodInterceptResult); + synchronousDispatcherInterceptor.afterMethod(enhancedInstance, null, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + AssertTools.assertHttpSpan(spans.get(0)); + } + + @Test + public void testWithSW6SerializedContextData() throws Throwable { + MultivaluedMapImpl multivaluedMap = new MultivaluedMapImpl(); + multivaluedMap.putSingle(SW8CarrierItem.HEADER_NAME, "1-My40LjU=-MS4yLjM=-3-c2VydmljZQ==-aW5zdGFuY2U=-L2FwcA==-MTI3LjAuMC4xOjgwODA="); + when(request.getHttpHeaders()).thenReturn(new ResteasyHttpHeaders(multivaluedMap)); + + synchronousDispatcherInterceptor.beforeMethod(enhancedInstance, null, arguments, argumentType, methodInterceptResult); + synchronousDispatcherInterceptor.afterMethod(enhancedInstance, null, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + AssertTools.assertHttpSpan(spans.get(0)); + AssertTools.assertTraceSegmentRef(traceSegment.getRef()); + } + + @Test + public void testWithOccurException() throws Throwable { + synchronousDispatcherInterceptor.beforeMethod(enhancedInstance, null, arguments, argumentType, methodInterceptResult); + synchronousDispatcherInterceptor.handleMethodException(enhancedInstance, null, arguments, argumentType, new RuntimeException()); + synchronousDispatcherInterceptor.afterMethod(enhancedInstance, null, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + AssertTools.assertHttpSpan(spans.get(0)); + List logDataEntities = SpanHelper.getLogs(spans.get(0)); + assertThat(logDataEntities.size(), is(1)); + SpanAssert.assertException(logDataEntities.get(0), RuntimeException.class); + } + + @Test + public void testWithMainThreadOccurException() throws Throwable { + synchronousDispatcherInterceptor.beforeMethod(enhancedInstance, null, arguments, argumentType, methodInterceptResult); + exceptionInterceptor.beforeMethod(enhancedInstance, null, exceptionArguments, exceptionArgumentType, null); + synchronousDispatcherInterceptor.afterMethod(enhancedInstance, null, arguments, argumentType, null); + + assertThat(segmentStorage.getTraceSegments().size(), is(1)); + TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0); + List spans = SegmentHelper.getSpans(traceSegment); + + AssertTools.assertHttpSpan(spans.get(0)); + List logDataEntities = SpanHelper.getLogs(spans.get(0)); + assertThat(logDataEntities.size(), is(1)); + SpanAssert.assertException(logDataEntities.get(0), RuntimeException.class); + } +} diff --git a/docs/en/setup/service-agent/java-agent/Plugin-list.md b/docs/en/setup/service-agent/java-agent/Plugin-list.md index d5d04c2dda..7647f0a4d5 100644 --- a/docs/en/setup/service-agent/java-agent/Plugin-list.md +++ b/docs/en/setup/service-agent/java-agent/Plugin-list.md @@ -74,6 +74,7 @@ - rabbitmq-5.x - redisson-3.x - resteasy-server-3.x +- resteasy-server-4.x - rocketMQ-3.x - rocketMQ-4.x - sentinel-1.x diff --git a/docs/en/setup/service-agent/java-agent/Supported-list.md b/docs/en/setup/service-agent/java-agent/Supported-list.md index 66d8d77f60..f566b9ad7e 100644 --- a/docs/en/setup/service-agent/java-agent/Supported-list.md +++ b/docs/en/setup/service-agent/java-agent/Supported-list.md @@ -15,7 +15,7 @@ metrics based on the tracing data. * [Jetty Server](http://www.eclipse.org/jetty/) 9 * [Spring WebFlux](https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html) 5.x (Optional¹) * [Undertow](http://undertow.io/) 1.3.0.Final -> 2.0.27.Final - * [RESTEasy](https://resteasy.github.io/) 3.1.0.Final -> 3.7.0.Final + * [RESTEasy](https://resteasy.github.io/) 3.1.0.Final -> 4.7.6.Final * [Play Framework](https://www.playframework.com/) 2.6.x -> 2.8.x * [Light4J Microservices Framework](https://doc.networknt.com/) 1.6.x -> 2.x * [Netty SocketIO](https://github.com/mrniko/netty-socketio) 1.x diff --git a/pom.xml b/pom.xml index 60974d15b4..27d80100ac 100755 --- a/pom.xml +++ b/pom.xml @@ -391,6 +391,7 @@ ${project.build.sourceDirectory} ${project.build.testSourceDirectory} scenarios/okhttp-scenario +scenarios/spring-4.3.x-scenario **/*.properties, diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/resteasy-4.x-scenario/config/expectedData.yaml new file mode 100644 index 0000000000..08dd31dfa5 --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/config/expectedData.yaml @@ -0,0 +1,190 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +segmentItems: + - serviceName: resteasy-4.x-scenario + segmentSize: ge 6 + segments: + - segmentId: not null + spans: + - operationName: HEAD:/resteasy-4.x-scenario/healthCheck + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/healthCheck' } + - { key: http.method, value: HEAD } + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: POST:/resteasy-4.x-scenario/create/ + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/create/' } + - { key: http.method, value: POST } + refs: + - { parentEndpoint: 'GET:/resteasy-4.x-scenario/case/resttemplate', networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: resteasy-4.x-scenario, traceId: not null } + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: GET:/resteasy-4.x-scenario/get/1 + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/get/1' } + - { key: http.method, value: GET } + refs: + - { parentEndpoint: 'GET:/resteasy-4.x-scenario/case/resttemplate', networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: resteasy-4.x-scenario, traceId: not null } + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: PUT:/resteasy-4.x-scenario/update/1 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/update/1' } + - { key: http.method, value: PUT } + refs: + - { parentEndpoint: 'GET:/resteasy-4.x-scenario/case/resttemplate', networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 3, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: resteasy-4.x-scenario, traceId: not null } + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: DELETE:/resteasy-4.x-scenario/delete/1 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/delete/1' } + - { key: http.method, value: DELETE } + refs: + - { parentEndpoint: 'GET:/resteasy-4.x-scenario/case/resttemplate', networkAddress: 'localhost:8080', refType: CrossProcess, + parentSpanId: 4, parentTraceSegmentId: not null, parentServiceInstance: not + null, parentService: resteasy-4.x-scenario, traceId: not null } + skipAnalysis: 'false' + - segmentId: not null + spans: + - operationName: /resteasy-4.x-scenario/create/ + parentSpanId: 0 + spanId: 1 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 2 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/create/' } + - { key: http.method, value: POST } + skipAnalysis: 'false' + - operationName: /resteasy-4.x-scenario/get/1 + parentSpanId: 0 + spanId: 2 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 2 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/get/1' } + - { key: http.method, value: GET } + skipAnalysis: 'false' + - operationName: /resteasy-4.x-scenario/update/1 + parentSpanId: 0 + spanId: 3 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 2 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/update/1' } + - { key: http.method, value: PUT } + skipAnalysis: 'false' + - operationName: /resteasy-4.x-scenario/delete/1 + parentSpanId: 0 + spanId: 4 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 2 + isError: false + spanType: Exit + peer: localhost:8080 + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/delete/1' } + - { key: http.method, value: DELETE } + skipAnalysis: 'false' + - operationName: GET:/resteasy-4.x-scenario/case/resttemplate + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Http + startTime: nq 0 + endTime: nq 0 + componentId: 1 + isError: false + spanType: Entry + peer: '' + skipAnalysis: false + tags: + - { key: url, value: 'http://localhost:8080/resteasy-4.x-scenario/case/resttemplate' } + - { key: http.method, value: GET } \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/configuration.yml b/test/plugin/scenarios/resteasy-4.x-scenario/configuration.yml new file mode 100644 index 0000000000..f17c8b9661 --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/configuration.yml @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +type: tomcat +entryService: http://localhost:8080/resteasy-4.x-scenario/case/resttemplate +healthCheck: http://localhost:8080/resteasy-4.x-scenario/healthCheck +environment: + - CATALINA_OPTS="-Dskywalking.plugin.http.include_http_headers=mock_header" \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/pom.xml b/test/plugin/scenarios/resteasy-4.x-scenario/pom.xml new file mode 100644 index 0000000000..a0b86fa663 --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/pom.xml @@ -0,0 +1,96 @@ + + + + 4.0.0 + + org.apache.skywalking + resteasy-4.x-scenario + 1.0.0 + war + + + UTF-8 + 1.8 + 4.7.6.Final + 2.8.1 + + + + + + org.jboss.resteasy + resteasy-core + ${test.framework.version} + + + + org.jboss.resteasy + resteasy-jaxb-provider + ${test.framework.version} + + + + org.jboss.resteasy + resteasy-servlet-initializer + ${test.framework.version} + + + + + org.jboss.resteasy + resteasy-jackson2-provider + ${test.framework.version} + + + + org.jboss.resteasy + resteasy-client + ${test.framework.version} + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + + + + resteasy-4.x-scenario + + + maven-compiler-plugin + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/RestTemplateController.java b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/RestTemplateController.java new file mode 100644 index 0000000000..fc0d33d555 --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/RestTemplateController.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package test.apache.skywalking.apm.testcase; + +import test.apache.skywalking.apm.testcase.entity.User; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; + +@Path("/") +public class RestTemplateController { + + private static final String SUCCESS = "Success"; + + private static final String URL = "http://localhost:8080/resteasy-4.x-scenario"; + + @GET() + @Path("/case/resttemplate") + public Response restTemplate() { + Client client = ClientBuilder.newBuilder().build(); + + // Create user + User userEntity = new User(1, "a"); + WebTarget target = client.target(URL + "/create/"); + Response response = target.request().post(Entity.json(userEntity)); + String value = response.readEntity(String.class); + response.close(); + + // Find User + response = client.target(URL + "/get/1").request().get(); + response.close(); + + //Modify user + User updateUserEntity = new User(1, "b"); + response = client.target(URL + "/update/1").request().put(Entity.json(updateUserEntity)); + response.close(); + + //Delete user + response = client.target(URL + "/delete/1").request().delete(); + response.close(); + + client.close(); + + return Response.ok(SUCCESS).build(); + } + + @GET + @Path("/healthCheck") + public String healthCheck() { + return SUCCESS; + } + +} \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/UserApplication.java b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/UserApplication.java new file mode 100644 index 0000000000..fbff4e2f5a --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/UserApplication.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package test.apache.skywalking.apm.testcase; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; +import java.util.HashSet; +import java.util.Set; + +@ApplicationPath("/") +public class UserApplication extends Application { + private Set singletons = new HashSet(); + private Set> empty = new HashSet>(); + + public UserApplication() { + singletons.add(new UserResource()); + singletons.add(new RestTemplateController()); + } + + @Override + public Set> getClasses() { + return empty; + } + + @Override + public Set getSingletons() { + return singletons; + } +} \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/UserResource.java b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/UserResource.java new file mode 100644 index 0000000000..1781785897 --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/UserResource.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package test.apache.skywalking.apm.testcase; + +import test.apache.skywalking.apm.testcase.entity.User; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Path("/") +public class UserResource { + + private static final Map USERS = new ConcurrentHashMap<>(); + + @GET() + @Path("/get/{id}") + @Produces(MediaType.APPLICATION_JSON) + public Response getUser(@PathParam("id") int id) { + User currentUser = new User(id, "a"); + return Response.ok(currentUser).build(); + } + + @POST + @Path("/create/") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response createUser(User user) { + USERS.put(user.getId(), user); + return Response.created(URI.create("")).build(); + } + + @PUT + @Path("/update/{id}") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response updateUser(@PathParam("id") int id, User user) { + User currentUser = new User(id, user.getUserName()); + return Response.ok(currentUser).build(); + } + + @DELETE() + @Path("/delete/{id}") + public Response deleteUser(@PathParam("id") int id) { + User currentUser = USERS.get(id); + if (currentUser == null) { + return Response.noContent().build(); + } + USERS.remove(id); + return Response.noContent().build(); + } +} \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java new file mode 100644 index 0000000000..547f1e3572 --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/entity/User.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package test.apache.skywalking.apm.testcase.entity; + +import java.io.Serializable; + +public class User implements Serializable { + + private int id; + private String userName; + + public User(int id) { + this.id = id; + } + + public User(int id, String userName) { + this.id = id; + this.userName = userName; + } + + public User() { + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..9849ed5a8a --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/src/main/webapp/WEB-INF/web.xml b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..67b3996cde --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,39 @@ + + + + resteasy-4.x-scenario + + resteasy-4.x-scenario + org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher + + javax.ws.rs.Application + test.apache.skywalking.apm.testcase.UserApplication + + + + + resteasy-4.x-scenario + /* + + + \ No newline at end of file diff --git a/test/plugin/scenarios/resteasy-4.x-scenario/support-version.list b/test/plugin/scenarios/resteasy-4.x-scenario/support-version.list new file mode 100644 index 0000000000..f671bc5b79 --- /dev/null +++ b/test/plugin/scenarios/resteasy-4.x-scenario/support-version.list @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# test all 4.x version, with corresponding latest patch version +4.7.6.Final +4.6.2.Final +4.5.12.Final +4.4.3.Final +4.3.1.Final +4.2.0.Final +4.1.1.Final +4.0.0.Final \ No newline at end of file