From 84fed02b74025e018309b5b679fcf8051901894a Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 8 May 2024 20:20:22 +0800 Subject: [PATCH 01/20] feat: split servlet to v3 and v5 for requestHandler (#473) * feat: split servlet to v3 and v5 for requestHandler * feat: log servlet version * feat: split servletRequestHandler to v3 and v5 * feat: split servlet to v3 and v5 for requestHandler * feat: add UT * fix: sonar --- .../inst/runtime/model/ArexConstants.java | 2 + .../MergeRecordServletRequestHandler.java | 30 ------ .../request/RequestHandlerManager.java | 20 ++-- .../MergeRecordDubboRequestHandlerTest.java | 18 ---- .../MergeRecordServletRequestHandlerTest.java | 18 ---- .../config/arex-apollo/pom.xml | 6 ++ .../apollo/ApolloServletV3RequestHandler.java | 3 +- .../apollo/ApolloServletV5RequestHandler.java | 57 +++++++++++ .../ApolloServletV5RequestHandlerTest.java | 96 +++++++++++++++++++ .../dubbo/common/DubboRequestHandler.java | 5 +- .../dubbo/common/DubboRequestHandlerTest.java | 42 ++++++++ .../inst/httpservlet/ServletAdviceHelper.java | 7 +- .../httpservlet/adapter/ServletAdapter.java | 2 + .../adapter/impl/ServletAdapterImplV3.java | 6 ++ .../adapter/impl/ServletAdapterImplV5.java | 6 ++ .../handler/ServletV3RequestHandler.java | 34 +++++++ .../handler/ServletV5RequestHandler.java | 33 +++++++ .../httpservlet/ServletAdviceHelperTest.java | 1 + .../impl/ServletAdapterImplV3Test.java | 5 + .../impl/ServletAdapterImplV5Test.java | 6 ++ .../handler/ServletV3RequestHandlerTest.java | 42 ++++++++ .../handler/ServletV5RequestHandlerTest.java | 42 ++++++++ 22 files changed, 397 insertions(+), 84 deletions(-) delete mode 100644 arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandler.java delete mode 100644 arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandlerTest.java delete mode 100644 arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandlerTest.java create mode 100644 arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandler.java create mode 100644 arex-instrumentation/config/arex-apollo/src/test/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandlerTest.java rename arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandler.java => arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboRequestHandler.java (80%) create mode 100644 arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboRequestHandlerTest.java create mode 100644 arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandler.java create mode 100644 arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandler.java create mode 100644 arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandlerTest.java create mode 100644 arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandlerTest.java diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java index 9044d0d77..b9e3dc5eb 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java @@ -59,4 +59,6 @@ private ArexConstants() {} public static final String EXCEED_MAX_SIZE_TITLE = "exceed.max.size"; public static final String EXCEED_MAX_SIZE_FLAG = "isExceedMaxSize"; public static final String RECORD_SIZE_LIMIT = "arex.record.size.limit"; + public static final String SERVLET_V3 = "ServletV3"; + public static final String SERVLET_V5 = "ServletV5"; } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandler.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandler.java deleted file mode 100644 index 67ac8e607..000000000 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.arex.inst.runtime.request; - -import com.google.auto.service.AutoService; -import io.arex.agent.bootstrap.model.MockCategoryType; -import io.arex.inst.runtime.util.MergeRecordReplayUtil; - - -@AutoService(RequestHandler.class) -public class MergeRecordServletRequestHandler implements RequestHandler { - @Override - public String name() { - return MockCategoryType.SERVLET.getName(); - } - - @Override - public void preHandle(Object request) { - // no need implement - } - - @Override - public void handleAfterCreateContext(Object request) { - // init replay and cached dynamic class - MergeRecordReplayUtil.mergeReplay(); - } - - @Override - public void postHandle(Object request, Object response) { - // no need implement - } -} diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/RequestHandlerManager.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/RequestHandlerManager.java index e2b841d88..b5139cd14 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/RequestHandlerManager.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/RequestHandlerManager.java @@ -28,7 +28,7 @@ public static void preHandle(Object request, String name) { requestHandler.preHandle(request); } catch (Throwable ex) { // avoid affecting the remaining handlers when one handler fails - LogManager.warn("preHandler", ex.getMessage()); + LogManager.warn("preHandler", ex); } } } @@ -43,22 +43,22 @@ public static void handleAfterCreateContext(Object request, String name) { requestHandler.handleAfterCreateContext(request); } catch (Throwable ex) { // avoid affecting the remaining handlers when one handler fails - LogManager.warn("handleAfterCreateContext", ex.getMessage()); + LogManager.warn("handleAfterCreateContext", ex); } } } public static void postHandle(Object request, Object response, String name) { - try { - final List requestHandlers = REQUEST_HANDLER_CACHE.get(name); - if (CollectionUtil.isEmpty(requestHandlers)) { - return; - } - for (RequestHandler requestHandler : requestHandlers) { + final List requestHandlers = REQUEST_HANDLER_CACHE.get(name); + if (CollectionUtil.isEmpty(requestHandlers)) { + return; + } + for (RequestHandler requestHandler : requestHandlers) { + try { requestHandler.postHandle(request, response); + } catch (Throwable ex) { + LogManager.warn("postHandle", ex); } - } catch (Throwable ex) { - LogManager.warn("postHandle", ex); } } } diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandlerTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandlerTest.java deleted file mode 100644 index 57783b3b4..000000000 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandlerTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.arex.inst.runtime.request; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class MergeRecordDubboRequestHandlerTest { - - @Test - void name() { - assertNotNull(new MergeRecordDubboRequestHandler().name()); - } - - @Test - void handleAfterCreateContext() { - assertDoesNotThrow(() -> new MergeRecordDubboRequestHandler().handleAfterCreateContext("mock")); - } -} \ No newline at end of file diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandlerTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandlerTest.java deleted file mode 100644 index 74cf18180..000000000 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/request/MergeRecordServletRequestHandlerTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.arex.inst.runtime.request; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class MergeRecordServletRequestHandlerTest { - - @Test - void name() { - assertNotNull(new MergeRecordServletRequestHandler().name()); - } - - @Test - void handleAfterCreateContext() { - assertDoesNotThrow(() -> new MergeRecordServletRequestHandler().handleAfterCreateContext("mock")); - } -} \ No newline at end of file diff --git a/arex-instrumentation/config/arex-apollo/pom.xml b/arex-instrumentation/config/arex-apollo/pom.xml index 0e2d0d6eb..aacf140f5 100644 --- a/arex-instrumentation/config/arex-apollo/pom.xml +++ b/arex-instrumentation/config/arex-apollo/pom.xml @@ -24,6 +24,12 @@ 3.1.0 provided + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + provided + \ No newline at end of file diff --git a/arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV3RequestHandler.java b/arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV3RequestHandler.java index 8f1e524a8..bbca20eca 100644 --- a/arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV3RequestHandler.java +++ b/arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV3RequestHandler.java @@ -3,7 +3,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.auto.service.AutoService; -import io.arex.agent.bootstrap.model.MockCategoryType; import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.model.ArexConstants; import io.arex.inst.runtime.request.RequestHandler; @@ -12,7 +11,7 @@ public class ApolloServletV3RequestHandler implements RequestHandler { @Override public String name() { - return MockCategoryType.SERVLET.getName(); + return ArexConstants.SERVLET_V3; } @Override diff --git a/arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandler.java b/arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandler.java new file mode 100644 index 000000000..7f087fa7b --- /dev/null +++ b/arex-instrumentation/config/arex-apollo/src/main/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandler.java @@ -0,0 +1,57 @@ +package io.arex.inst.config.apollo; + +import com.google.auto.service.AutoService; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.request.RequestHandler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@AutoService(RequestHandler.class) +public class ApolloServletV5RequestHandler implements RequestHandler { + @Override + public String name() { + return ArexConstants.SERVLET_V5; + } + + @Override + public void preHandle(HttpServletRequest request) { + // no need implement + } + + @Override + public void handleAfterCreateContext(HttpServletRequest request) { + // check business application if loaded apollo-client + if (ApolloConfigChecker.unloadApollo()) { + return; + } + ApolloConfigHelper.initAndRecord( + () -> request.getHeader(ArexConstants.RECORD_ID), + () -> request.getHeader(ArexConstants.CONFIG_DEPENDENCY)); + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response) { + if (postInvalid(request, response)) { + return; + } + /* + The reason for recording in postHandle() is to ensure that both full and increment configurations can be recorded, + because some configurations are initialized only when the code is executed in a specific scenario, + it may not record these configurations during preHandle() + */ + ApolloConfigHelper.recordAllConfigs(); + request.setAttribute(ArexConstants.CONFIG_VERSION, ApolloConfigExtractor.getCurrentRecordConfigVersion()); + } + + private boolean postInvalid(HttpServletRequest request, HttpServletResponse response) { + if (request == null || response == null) { + return true; + } + if (response.getHeader(ArexConstants.RECORD_ID) != null) { + return true; + } + return !ContextManager.needRecord() || ApolloConfigChecker.unloadApollo(); + } +} diff --git a/arex-instrumentation/config/arex-apollo/src/test/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandlerTest.java b/arex-instrumentation/config/arex-apollo/src/test/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandlerTest.java new file mode 100644 index 000000000..275affaf5 --- /dev/null +++ b/arex-instrumentation/config/arex-apollo/src/test/java/io/arex/inst/config/apollo/ApolloServletV5RequestHandlerTest.java @@ -0,0 +1,96 @@ +package io.arex.inst.config.apollo; + +import io.arex.agent.bootstrap.util.Assert; +import io.arex.inst.runtime.config.Config; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.model.ArexConstants; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.times; + +class ApolloServletV5RequestHandlerTest { + static ApolloServletV5RequestHandler target; + static MockedStatic mockStaticHelper; + + @BeforeAll + static void setUp() { + target = new ApolloServletV5RequestHandler(); + mockStaticHelper = Mockito.mockStatic(ApolloConfigHelper.class); + Mockito.mockStatic(ContextManager.class); + Mockito.mockStatic(Config.class); + Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); + Mockito.when(Config.get().getString(any(), any())).thenReturn("mock"); + } + + @AfterAll + static void tearDown() { + mockStaticHelper = null; + target = null; + Mockito.clearAllCaches(); + } + + @Test + void name() { + assertNotNull(target.name()); + } + + @Test + void handleAfterCreateContext() { + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + target.handleAfterCreateContext(request); + mockStaticHelper.verify(() -> ApolloConfigHelper.initAndRecord(any(), any()), atLeastOnce()); + } + + @ParameterizedTest + @MethodSource("postHandleCase") + void postHandle(Runnable mocker, HttpServletRequest request, HttpServletResponse response, Assert asserts) { + mocker.run(); + target.postHandle(request, response); + asserts.verity(); + } + + static Stream postHandleCase() { + Supplier requestSupplier = () -> Mockito.mock(HttpServletRequest.class); + + Supplier responseSupplier = () -> Mockito.mock(HttpServletResponse.class); + Supplier recordIdSupplier = () -> { + HttpServletResponse response = responseSupplier.get(); + Mockito.when(response.getHeader(ArexConstants.RECORD_ID)).thenReturn("mock"); + return response; + }; + + Runnable emptyMocker = () -> {}; + Runnable mocker1 = () -> { + Mockito.when(ContextManager.needRecord()).thenReturn(true); + }; + + Assert asserts1 = () -> { + mockStaticHelper.verify(ApolloConfigHelper::recordAllConfigs, times(0)); + }; + Assert asserts2 = () -> { + mockStaticHelper.verify(ApolloConfigHelper::recordAllConfigs, times(1)); + }; + + return Stream.of( + arguments(emptyMocker, null, null, asserts1), + arguments(mocker1, requestSupplier.get(), responseSupplier.get(), asserts2), + arguments(emptyMocker, requestSupplier.get(), recordIdSupplier.get(), asserts2) + ); + } +} \ No newline at end of file diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandler.java b/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboRequestHandler.java similarity index 80% rename from arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandler.java rename to arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboRequestHandler.java index e59fdee9d..b18ab22cd 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/request/MergeRecordDubboRequestHandler.java +++ b/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboRequestHandler.java @@ -1,12 +1,13 @@ -package io.arex.inst.runtime.request; +package io.arex.inst.dubbo.common; import com.google.auto.service.AutoService; import io.arex.agent.bootstrap.model.MockCategoryType; +import io.arex.inst.runtime.request.RequestHandler; import io.arex.inst.runtime.util.MergeRecordReplayUtil; @AutoService(RequestHandler.class) -public class MergeRecordDubboRequestHandler implements RequestHandler { +public class DubboRequestHandler implements RequestHandler { @Override public String name() { return MockCategoryType.DUBBO_PROVIDER.getName(); diff --git a/arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboRequestHandlerTest.java b/arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboRequestHandlerTest.java new file mode 100644 index 000000000..77d2dc986 --- /dev/null +++ b/arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboRequestHandlerTest.java @@ -0,0 +1,42 @@ +package io.arex.inst.dubbo.common; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class DubboRequestHandlerTest { + + static DubboRequestHandler target; + + @BeforeAll + static void setUp() { + target = new DubboRequestHandler(); + } + + @AfterAll + static void tearDown() { + target = null; + } + + @Test + void name() { + assertNotNull(target.name()); + } + + @Test + void preHandle() { + assertDoesNotThrow(() -> target.preHandle(null)); + } + + @Test + void handleAfterCreateContext() { + assertDoesNotThrow(() -> target.handleAfterCreateContext(null)); + } + + @Test + void postHandle() { + assertDoesNotThrow(() -> target.postHandle(null, null)); + } +} \ No newline at end of file diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java index 9854d677e..4ea02032b 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java @@ -3,7 +3,6 @@ import io.arex.agent.bootstrap.TraceContextManager; import io.arex.agent.bootstrap.constants.ConfigConstants; import io.arex.agent.bootstrap.internal.Pair; -import io.arex.agent.bootstrap.model.MockCategoryType; import io.arex.agent.bootstrap.util.CollectionUtil; import io.arex.agent.bootstrap.util.StringUtil; import io.arex.inst.httpservlet.adapter.ServletAdapter; @@ -104,7 +103,7 @@ public static Pair onServiceEnter( return null; } - RequestHandlerManager.preHandle(httpServletRequest, MockCategoryType.SERVLET.getName()); + RequestHandlerManager.preHandle(httpServletRequest, adapter.getServletVersion()); // skip servlet if attr with arex-skip-flag if (Boolean.TRUE.equals(adapter.getAttribute(httpServletRequest, ArexConstants.SKIP_FLAG))) { return null; @@ -121,7 +120,7 @@ public static Pair onServiceEnter( CaseEventDispatcher.onEvent(CaseEvent.ofCreateEvent(EventSource.of(caseId, excludeMockTemplate))); ContextManager.currentContext().setAttachment(ArexConstants.FORCE_RECORD, adapter.getRequestHeader(httpServletRequest, ArexConstants.FORCE_RECORD, ArexConstants.HEADER_X_PREFIX)); - RequestHandlerManager.handleAfterCreateContext(httpServletRequest, MockCategoryType.SERVLET.getName()); + RequestHandlerManager.handleAfterCreateContext(httpServletRequest, adapter.getServletVersion()); } if (ContextManager.needRecordOrReplay()) { @@ -140,7 +139,7 @@ public static void onServiceExit( TRequest httpServletRequest = adapter.asHttpServletRequest(servletRequest); TResponse httpServletResponse = adapter.asHttpServletResponse(servletResponse); - RequestHandlerManager.postHandle(httpServletRequest, httpServletResponse, MockCategoryType.SERVLET.getName()); + RequestHandlerManager.postHandle(httpServletRequest, httpServletResponse, adapter.getServletVersion()); if (httpServletRequest == null || httpServletResponse == null) { return; diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/ServletAdapter.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/ServletAdapter.java index 24601dfc3..b84f019ab 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/ServletAdapter.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/ServletAdapter.java @@ -98,4 +98,6 @@ default String getParameterFromQueryString(HttpServletRequest httpServletRequest end = end < 0 ? queryString.length() : end; return StringUtil.substring(queryString, start, end); } + + String getServletVersion(); } diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3.java index 9ac03c8de..7549e45dd 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3.java @@ -6,6 +6,7 @@ import io.arex.inst.httpservlet.wrapper.CachedBodyRequestWrapperV3; import io.arex.inst.httpservlet.wrapper.CachedBodyResponseWrapperV3; import io.arex.inst.httpservlet.listener.ServletAsyncListenerV3; +import io.arex.inst.runtime.model.ArexConstants; import org.springframework.web.context.request.NativeWebRequest; import javax.annotation.Nullable; @@ -229,4 +230,9 @@ public boolean markProcessed(HttpServletRequest httpServletRequest, String mark) public String getQueryString(HttpServletRequest servletRequest) { return servletRequest.getQueryString(); } + + @Override + public String getServletVersion() { + return ArexConstants.SERVLET_V3; + } } diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5.java index 6a78d4254..77306760d 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5.java @@ -6,6 +6,7 @@ import io.arex.inst.httpservlet.listener.ServletAsyncListenerV5; import io.arex.inst.httpservlet.wrapper.CachedBodyRequestWrapperV5; import io.arex.inst.httpservlet.wrapper.CachedBodyResponseWrapperV5; +import io.arex.inst.runtime.model.ArexConstants; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.context.request.NativeWebRequest; @@ -229,4 +230,9 @@ public boolean markProcessed(HttpServletRequest httpServletRequest, String mark) public String getQueryString(HttpServletRequest httpServletRequest) { return httpServletRequest.getQueryString(); } + + @Override + public String getServletVersion() { + return ArexConstants.SERVLET_V5; + } } diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandler.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandler.java new file mode 100644 index 000000000..02232d47e --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandler.java @@ -0,0 +1,34 @@ +package io.arex.inst.httpservlet.handler; + +import com.google.auto.service.AutoService; +import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.request.RequestHandler; +import io.arex.inst.runtime.util.MergeRecordReplayUtil; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +@AutoService(RequestHandler.class) +public class ServletV3RequestHandler implements RequestHandler { + @Override + public String name() { + return ArexConstants.SERVLET_V3; + } + + @Override + public void preHandle(HttpServletRequest request) { + // no need implement + } + + @Override + public void handleAfterCreateContext(HttpServletRequest request) { + // init replay and cached dynamic class + MergeRecordReplayUtil.mergeReplay(); + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response) { + // no need implement + } +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandler.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandler.java new file mode 100644 index 000000000..64d0191ba --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandler.java @@ -0,0 +1,33 @@ +package io.arex.inst.httpservlet.handler; + +import com.google.auto.service.AutoService; +import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.request.RequestHandler; +import io.arex.inst.runtime.util.MergeRecordReplayUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + + +@AutoService(RequestHandler.class) +public class ServletV5RequestHandler implements RequestHandler { + @Override + public String name() { + return ArexConstants.SERVLET_V5; + } + + @Override + public void preHandle(HttpServletRequest request) { + // no need implement + } + + @Override + public void handleAfterCreateContext(HttpServletRequest request) { + // init replay and cached dynamic class + MergeRecordReplayUtil.mergeReplay(); + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response) { + // no need implement + } +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java index b621977f4..d3233f292 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java @@ -61,6 +61,7 @@ static void setUp() { Mockito.mockStatic(IgnoreUtils.class); Mockito.mockStatic(Config.class); Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); + Mockito.when(adapter.getServletVersion()).thenReturn("mock"); } @AfterAll diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3Test.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3Test.java index 4cec2655d..479fed496 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3Test.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV3Test.java @@ -301,4 +301,9 @@ void getParameterFromQueryString(String queryString) { String redirectRecordId = instance.getParameterFromQueryString(mockRequest, "arex-record-id"); assertEquals("mock-redirectRecordId", redirectRecordId); } + + @Test + void getServletVersion() { + assertEquals(ArexConstants.SERVLET_V3, instance.getServletVersion()); + } } diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5Test.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5Test.java index a3335ad6d..aea045d99 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5Test.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/adapter/impl/ServletAdapterImplV5Test.java @@ -7,6 +7,7 @@ import io.arex.inst.httpservlet.wrapper.CachedBodyRequestWrapperV5; import io.arex.inst.httpservlet.wrapper.CachedBodyResponseWrapperV5; +import io.arex.inst.runtime.model.ArexConstants; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import java.io.ByteArrayInputStream; @@ -272,4 +273,9 @@ void getQueryString() { when(mockRequest.getQueryString()).thenReturn("k1=v1&k2=v2"); assertEquals("k1=v1&k2=v2", instance.getQueryString(mockRequest)); } + + @Test + void getServletVersion() { + assertEquals(ArexConstants.SERVLET_V5, instance.getServletVersion()); + } } diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandlerTest.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandlerTest.java new file mode 100644 index 000000000..a942ef118 --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV3RequestHandlerTest.java @@ -0,0 +1,42 @@ +package io.arex.inst.httpservlet.handler; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ServletV3RequestHandlerTest { + + static ServletV3RequestHandler target; + + @BeforeAll + static void setUp() { + target = new ServletV3RequestHandler(); + } + + @AfterAll + static void tearDown() { + target = null; + } + + @Test + void name() { + assertNotNull(target.name()); + } + + @Test + void preHandle() { + assertDoesNotThrow(() -> target.preHandle(null)); + } + + @Test + void handleAfterCreateContext() { + assertDoesNotThrow(() -> target.handleAfterCreateContext(null)); + } + + @Test + void postHandle() { + assertDoesNotThrow(() -> target.postHandle(null, null)); + } +} \ No newline at end of file diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandlerTest.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandlerTest.java new file mode 100644 index 000000000..7186a7d05 --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/handler/ServletV5RequestHandlerTest.java @@ -0,0 +1,42 @@ +package io.arex.inst.httpservlet.handler; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ServletV5RequestHandlerTest { + + static ServletV5RequestHandler target; + + @BeforeAll + static void setUp() { + target = new ServletV5RequestHandler(); + } + + @AfterAll + static void tearDown() { + target = null; + } + + @Test + void name() { + assertNotNull(target.name()); + } + + @Test + void preHandle() { + assertDoesNotThrow(() -> target.preHandle(null)); + } + + @Test + void handleAfterCreateContext() { + assertDoesNotThrow(() -> target.handleAfterCreateContext(null)); + } + + @Test + void postHandle() { + assertDoesNotThrow(() -> target.postHandle(null, null)); + } +} \ No newline at end of file From f12ec92b6f325cdb0c307a8fed6ce6730fd15b78 Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Fri, 10 May 2024 10:29:15 +0800 Subject: [PATCH 02/20] feat: abstract class method obtains the name of the calling class (#475) --- .../dynamic/common/DynamicClassExtractor.java | 24 ++++++++++++-- .../common/DynamicClassExtractorTest.java | 33 +++++++++++++++---- .../dynamic/DynamicClassInstrumentation.java | 3 +- .../DynamicClassInstrumentationTest.java | 4 +-- 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/DynamicClassExtractor.java b/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/DynamicClassExtractor.java index 763d9b799..16897afbc 100644 --- a/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/DynamicClassExtractor.java +++ b/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/DynamicClassExtractor.java @@ -74,7 +74,7 @@ public DynamicClassExtractor(Method method, Object[] args, String keyExpression, this.requestType = buildRequestType(method); } - public DynamicClassExtractor(Method method, Object[] args) { + public DynamicClassExtractor(Method method, Object[] args, Object target) { int proceedingJoinPointIndex = proceedingJoinPointIndex(method.getParameterTypes()); if (proceedingJoinPointIndex != -1) { ProceedingJoinPoint proceedingJoinPoint = (ProceedingJoinPoint) args[proceedingJoinPointIndex]; @@ -84,7 +84,7 @@ public DynamicClassExtractor(Method method, Object[] args) { this.args = proceedingJoinPoint.getArgs(); this.requestType = ArrayUtils.toString(this.args, o -> o.getClass().getTypeName()); } else { - this.clazzName = normalizeClassName(method.getDeclaringClass().getName()); + this.clazzName = normalizeClassName(getTargetClassName(method.getDeclaringClass(), target)); this.methodName = method.getName(); this.args = args; this.requestType = ArrayUtils.toString(method.getParameterTypes(), obj -> ((Class)obj).getTypeName()); @@ -402,6 +402,26 @@ private int buildNoArgMethodSignatureHash(boolean isNeedResult) { return StringUtil.encodeAndHash(String.format("%s_%s_%s", this.clazzName, this.methodName, null)); } + /** + * abstract class BaseCache{ + * Object method() { + * } + * } + * class subCache extends BaseCache{ + * Object methodB() { + * } + * } + * when call subCache.method() -> BaseCache.method(), the target class name is subCache + * when call subCache.methodB(), the target class name is subCache + * @param target maybe null when method is static + */ + private String getTargetClassName(Class clazz, Object target) { + if (target != null) { + return target.getClass().getName(); + } + return clazz.getName(); + } + String normalizeClassName(String className) { // ex: com.xxx.xxx.client$$EnhancerBySpringCGLIB$$1e3f5g if (StringUtil.isNotEmpty(className) && className.contains("CGLIB$$")) { diff --git a/arex-instrumentation/dynamic/arex-dynamic-common/src/test/java/io/arex/inst/dynamic/common/DynamicClassExtractorTest.java b/arex-instrumentation/dynamic/arex-dynamic-common/src/test/java/io/arex/inst/dynamic/common/DynamicClassExtractorTest.java index 29f3cb9d0..a5d46fdfa 100644 --- a/arex-instrumentation/dynamic/arex-dynamic-common/src/test/java/io/arex/inst/dynamic/common/DynamicClassExtractorTest.java +++ b/arex-instrumentation/dynamic/arex-dynamic-common/src/test/java/io/arex/inst/dynamic/common/DynamicClassExtractorTest.java @@ -96,7 +96,7 @@ void record(Runnable mocker, Object[] args, Object result, Predicate pre return null; }); Method testWithArexMock = getMethod(result); - DynamicClassExtractor extractor = new DynamicClassExtractor(testWithArexMock, args); + DynamicClassExtractor extractor = new DynamicClassExtractor(testWithArexMock, args, null); extractor.recordResponse(result); assertTrue(predicate.test(result)); @@ -125,7 +125,7 @@ void resetMonoResponse() { Method testWithArexMock = DynamicClassExtractorTest.class.getDeclaredMethod("testWithArexMock", String.class); final Object[] args = {"errorSerialize"}; - final DynamicClassExtractor extractor = new DynamicClassExtractor(testWithArexMock, args); + final DynamicClassExtractor extractor = new DynamicClassExtractor(testWithArexMock, args, null); final Predicate nonNull = Objects::nonNull; // exception @@ -427,10 +427,10 @@ void invalidOperation() throws Throwable { final Object[] args = {"errorSerialize"}; ConfigBuilder.create("invalid-operation").enableDebug(true).build(); Mockito.when(Serializer.serializeWithException(any(), anyString())).thenThrow(new RuntimeException("errorSerialize")); - DynamicClassExtractor extractor = new DynamicClassExtractor(testWithArexMock, args); + DynamicClassExtractor extractor = new DynamicClassExtractor(testWithArexMock, args, null); extractor.recordResponse("errorSerialize"); // invalid operation return empty - DynamicClassExtractor extractor2 = new DynamicClassExtractor(testWithArexMock, args); + DynamicClassExtractor extractor2 = new DynamicClassExtractor(testWithArexMock, args, null); final Field methodKey = DynamicClassExtractor.class.getDeclaredField("methodKey"); methodKey.setAccessible(true); assertNull(methodKey.get(extractor2)); @@ -442,14 +442,14 @@ void invalidOperation() throws Throwable { // test getSerializedResult serialize Mockito.when(agentSizeOf.checkMemorySizeLimit(any(), any(long.class))).thenReturn(true); - extractor = new DynamicClassExtractor(testWithArexMock, args); + extractor = new DynamicClassExtractor(testWithArexMock, args, null); assertNull(extractor.getSerializedResult()); } @Test void emptyMethodKeyAndExceedSize() throws NoSuchMethodException { Method testEmptyArgs = DynamicClassExtractorTest.class.getDeclaredMethod("invalidOperation"); - DynamicClassExtractor extractor = new DynamicClassExtractor(testEmptyArgs, new Object[0]); + DynamicClassExtractor extractor = new DynamicClassExtractor(testEmptyArgs, new Object[0], null); assertDoesNotThrow(() -> extractor.recordResponse(new int[1001])); } @@ -566,7 +566,7 @@ void testProceedingJointPoint() throws Throwable { Mockito.when(joinPoint.getSignature()).thenReturn(signature); String arg1 = "arg1"; Mockito.when(joinPoint.getArgs()).thenReturn(new Object[]{arg1}); - DynamicClassExtractor extractor = new DynamicClassExtractor(testProceedingJointPoint, new Object[]{joinPoint}); + DynamicClassExtractor extractor = new DynamicClassExtractor(testProceedingJointPoint, new Object[]{joinPoint}, null); Field requestType = DynamicClassExtractor.class.getDeclaredField("requestType"); requestType.setAccessible(true); assertEquals("[\"java.lang.String\"]", requestType.get(extractor)); @@ -574,4 +574,23 @@ void testProceedingJointPoint() throws Throwable { public void testProceedingJointPoint(ProceedingJoinPoint joinPoint) { } + + @Test + void testRecordWithAbstractClassMethod() throws Exception { + CacheClass cacheClass = new CacheClass(); + Method abstractMethod = AbstractClass.class.getDeclaredMethod("abstractMethod", String.class); + Field clazzName = DynamicClassExtractor.class.getDeclaredField("clazzName"); + clazzName.setAccessible(true); + DynamicClassExtractor extractor = new DynamicClassExtractor(abstractMethod, new Object[]{"arg"}, cacheClass); + assertEquals(CacheClass.class.getName(), clazzName.get(extractor)); + } + + static abstract class AbstractClass { + public String abstractMethod(String arg) { + return arg; + } + } + + static class CacheClass extends AbstractClass { + } } diff --git a/arex-instrumentation/dynamic/arex-dynamic/src/main/java/io/arex/inst/dynamic/DynamicClassInstrumentation.java b/arex-instrumentation/dynamic/arex-dynamic/src/main/java/io/arex/inst/dynamic/DynamicClassInstrumentation.java index fbcd7f1b0..0e929fd46 100644 --- a/arex-instrumentation/dynamic/arex-dynamic/src/main/java/io/arex/inst/dynamic/DynamicClassInstrumentation.java +++ b/arex-instrumentation/dynamic/arex-dynamic/src/main/java/io/arex/inst/dynamic/DynamicClassInstrumentation.java @@ -143,6 +143,7 @@ public static final class MethodAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, suppress = Throwable.class) public static boolean onEnter(@Advice.Origin Method method, + @Advice.This(optional = true) Object target, @Advice.AllArguments Object[] args, @Advice.Local("extractor") DynamicClassExtractor extractor, @Advice.Local("mockResult") MockResult mockResult) { @@ -153,7 +154,7 @@ public static boolean onEnter(@Advice.Origin Method method, if (void.class.isAssignableFrom(method.getReturnType())) { return ContextManager.needReplay(); } - extractor = new DynamicClassExtractor(method, args); + extractor = new DynamicClassExtractor(method, args, target); } if (ContextManager.needReplay()) { mockResult = extractor.replay(); diff --git a/arex-instrumentation/dynamic/arex-dynamic/src/test/java/io/arex/inst/dynamic/DynamicClassInstrumentationTest.java b/arex-instrumentation/dynamic/arex-dynamic/src/test/java/io/arex/inst/dynamic/DynamicClassInstrumentationTest.java index 79d15a15b..6ba46ff63 100644 --- a/arex-instrumentation/dynamic/arex-dynamic/src/test/java/io/arex/inst/dynamic/DynamicClassInstrumentationTest.java +++ b/arex-instrumentation/dynamic/arex-dynamic/src/test/java/io/arex/inst/dynamic/DynamicClassInstrumentationTest.java @@ -179,13 +179,13 @@ void onEnter() throws NoSuchMethodException { Mockito.when(ContextManager.needRecordOrReplay()).thenReturn(true); Mockito.when(ContextManager.needRecord()).thenReturn(true); DynamicClassExtractor extractor = new DynamicClassExtractor(test1, new Object[]{"mock"}, "#val", null); - boolean actualResult = DynamicClassInstrumentation.MethodAdvice.onEnter(test1, new Object[]{ "name", 18 }, extractor, null); + boolean actualResult = DynamicClassInstrumentation.MethodAdvice.onEnter(test1, null, new Object[]{ "name", 18 }, extractor, null); assertFalse(actualResult); // replay Mockito.when(ContextManager.needRecord()).thenReturn(false); Mockito.when(ContextManager.needReplay()).thenReturn(true); - actualResult = DynamicClassInstrumentation.MethodAdvice.onEnter(test1, new Object[]{ "name", 18 }, extractor, null); + actualResult = DynamicClassInstrumentation.MethodAdvice.onEnter(test1, null, new Object[]{ "name", 18 }, extractor, null); assertTrue(actualResult); } } From 9a4bf6a5a4d3a7098abc23fad3f1036029c1cc73 Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Fri, 10 May 2024 11:37:30 +0800 Subject: [PATCH 03/20] fix: file mkdir false (#476) --- .../io/arex/agent/instrumentation/BaseAgentInstaller.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arex-agent-core/src/main/java/io/arex/agent/instrumentation/BaseAgentInstaller.java b/arex-agent-core/src/main/java/io/arex/agent/instrumentation/BaseAgentInstaller.java index 06bac3634..c02b4833e 100644 --- a/arex-agent-core/src/main/java/io/arex/agent/instrumentation/BaseAgentInstaller.java +++ b/arex-agent-core/src/main/java/io/arex/agent/instrumentation/BaseAgentInstaller.java @@ -152,12 +152,12 @@ private void createDumpDirectory() { if (exists) { FileUtils.cleanDirectory(bytecodeDumpPath); } else { - mkdir = bytecodeDumpPath.mkdir(); + mkdir = bytecodeDumpPath.mkdirs(); } LOGGER.info("[arex] bytecode dump path exists: {}, mkdir: {}, path: {}", exists, mkdir, bytecodeDumpPath.getPath()); System.setProperty(TypeWriter.DUMP_PROPERTY, bytecodeDumpPath.getPath()); } catch (Exception e) { - LOGGER.info("[arex] Failed to create directory to instrumented bytecode: {}", e.getMessage()); + LOGGER.warn("[arex] Failed to create directory to instrumented bytecode: ", e); } } } From cccadc18f4c35fefae8b3825e4bb6e5475e42eb7 Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Wed, 15 May 2024 14:08:18 +0800 Subject: [PATCH 04/20] fix: skip when dependency not init (#480) --- .../arex/agent/bootstrap/util/CollectionUtil.java | 11 +++++++++++ .../agent/bootstrap/util/CollectionUtilTest.java | 9 +++++++++ .../arex/inst/runtime/listener/EventProcessor.java | 1 - .../arex/inst/runtime/serializer/Serializer.java | 6 +++++- .../inst/runtime/serializer/SerializerTest.java | 11 +++++++---- .../io/arex/inst/database/mongo/MongoHelper.java | 5 +++-- .../dubbo/alibaba/DubboProviderExtractorTest.java | 6 +++++- .../apache/v2/DubboProviderExtractorTest.java | 6 +++++- .../apache/v3/DubboProviderExtractorTest.java | 6 +++++- .../io/arex/inst/dubbo/common/DubboExtractor.java | 4 ++++ .../arex/inst/dubbo/common/DubboExtractorTest.java | 12 ++++++++++-- .../cache/spring/SpringCacheInstrumentation.java | 2 +- .../netty/v3/server/RequestTracingHandler.java | 6 +++++- .../netty/v3/server/RequestTracingHandlerTest.java | 13 +++++++++++-- .../netty/v4/server/RequestTracingHandler.java | 6 +++++- .../netty/v4/server/RequestTracingHandlerTest.java | 12 ++++++++++-- .../arex/inst/httpservlet/ServletAdviceHelper.java | 5 +++++ .../inst/httpservlet/ServletAdviceHelperTest.java | 14 +++++++++++--- 18 files changed, 112 insertions(+), 23 deletions(-) diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java index 153ff23ad..34acd1fe8 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java @@ -39,6 +39,17 @@ public static List newArrayList(E... elements) { return list; } + @SafeVarargs + public static Set newHashSet(E... elements) { + if (elements == null) { + return new HashSet<>(); + } + + Set set = new HashSet<>(elements.length); + Collections.addAll(set, elements); + return set; + } + /** * split to multiple list by split count */ diff --git a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java index e45e4ce43..f62a54010 100644 --- a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java +++ b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java @@ -40,6 +40,15 @@ void newArrayList() { assertInstanceOf(ArrayList.class, actualResult); } + @Test + void newHashSet() { + Set actualResult = CollectionUtil.newHashSet(null); + assertInstanceOf(HashSet.class, actualResult); + + actualResult = CollectionUtil.newHashSet("test"); + assertInstanceOf(HashSet.class, actualResult); + } + @ParameterizedTest @MethodSource("splitCase") void split(List originalList, int splitCount, Predicate>> predicate) { diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/listener/EventProcessor.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/listener/EventProcessor.java index b68e0e3b7..33262f2af 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/listener/EventProcessor.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/listener/EventProcessor.java @@ -71,7 +71,6 @@ private static void initSerializer(ClassLoader contextClassLoader) { AdviceClassesCollector.INSTANCE.appendToClassLoaderSearch("jackson", contextClassLoader); } - Serializer.initSerializerConfigMap(); final List serializableList = ServiceLoader.load(StringSerializable.class, contextClassLoader); Serializer.builder(serializableList).build(); } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/serializer/Serializer.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/serializer/Serializer.java index 485bfd9c1..1d213854d 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/serializer/Serializer.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/serializer/Serializer.java @@ -38,10 +38,14 @@ public static Builder builder(List serializableList) { private final StringSerializable defaultSerializer; private final Map serializers; + static { + initSerializerConfigMap(); + } + /** * ex: DubboProvider:jackson,DubboConsumer:gson */ - public static void initSerializerConfigMap() { + private static void initSerializerConfigMap() { try { String serializerConfig = Config.get().getString(ConfigConstants.SERIALIZER_CONFIG); if (StringUtil.isEmpty(serializerConfig)) { diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java index 677625a28..bdc59d0b7 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java @@ -10,7 +10,7 @@ import io.arex.inst.runtime.model.ArexConstants; import io.arex.inst.runtime.util.TypeUtil; import java.lang.reflect.Field; -import java.lang.reflect.Type; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; @@ -134,23 +134,26 @@ void nullObjectOrType() { @Test void testInitSerializerConfigMap() throws Exception { + Method initSerializerConfigMap = Serializer.class.getDeclaredMethod("initSerializerConfigMap"); + initSerializerConfigMap.setAccessible(true); // null config final Field instance = Config.class.getDeclaredField("INSTANCE"); instance.setAccessible(true); instance.set(null, null); - Assertions.assertDoesNotThrow(Serializer::initSerializerConfigMap); + initSerializerConfigMap.invoke(null); + Assertions.assertDoesNotThrow(() -> Serializer.getSerializerFromType("dubboRequest")); // empty serializer config ConfigBuilder builder = new ConfigBuilder("testSerializer"); builder.build(); - Serializer.initSerializerConfigMap(); + initSerializerConfigMap.invoke(null); assertNull(Serializer.getSerializerFromType("dubboRequest")); // serializer config builder = new ConfigBuilder("testSerializer"); builder.addProperty(ConfigConstants.SERIALIZER_CONFIG, "soa:gson,dubboRequest:jackson,httpRequest"); builder.build(); - Serializer.initSerializerConfigMap(); + initSerializerConfigMap.invoke(null); assertEquals("jackson", Serializer.getSerializerFromType("dubboRequest")); assertEquals("gson", Serializer.getSerializerFromType("soa")); assertNull(Serializer.getSerializerFromType("httpRequest")); diff --git a/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java b/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java index 9ced30e92..ada21ea1e 100644 --- a/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java +++ b/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java @@ -1,9 +1,9 @@ package io.arex.inst.database.mongo; -import com.google.common.collect.Sets; import com.mongodb.MongoNamespace; import io.arex.agent.bootstrap.model.MockResult; +import io.arex.agent.bootstrap.util.CollectionUtil; import io.arex.agent.bootstrap.util.StringUtil; import io.arex.inst.database.common.DatabaseExtractor; import io.arex.inst.database.mongo.wrapper.ResultWrapper; @@ -12,6 +12,7 @@ import org.bson.Document; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -24,7 +25,7 @@ */ public class MongoHelper { private static final String ID_FIELD = "_id"; - private static final Set INSERT_METHODS = Sets.newHashSet("executeInsertOne", "executeInsertMany"); + private static final Set INSERT_METHODS = CollectionUtil.newHashSet("executeInsertOne", "executeInsertMany"); /** * used to separate keyHolder and keyHolderType */ diff --git a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractorTest.java b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractorTest.java index 9096bea7e..491b1af6a 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractorTest.java +++ b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractorTest.java @@ -8,6 +8,8 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.context.RecordLimiter; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; +import io.arex.inst.runtime.util.CaseManager; import io.arex.inst.runtime.util.IgnoreUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -45,6 +47,8 @@ static void setUp() { Mockito.mockStatic(Config.class); Mockito.when(ContextManager.currentContext()).thenReturn(ArexContext.of("mock")); Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); + Mockito.mockStatic(EventProcessor.class); + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(true); } @AfterAll @@ -88,4 +92,4 @@ static Stream onServiceExitCase() { arguments(mocker1, asserts2) ); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/test/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractorTest.java b/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/test/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractorTest.java index e4f49e6ab..4e493b68e 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/test/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractorTest.java +++ b/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/test/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractorTest.java @@ -5,6 +5,8 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.context.RecordLimiter; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; +import io.arex.inst.runtime.util.CaseManager; import io.arex.inst.runtime.util.IgnoreUtils; import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcInvocation; @@ -43,6 +45,8 @@ static void setUp() { Mockito.mockStatic(Config.class); Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); Mockito.when(ContextManager.currentContext()).thenReturn(ArexContext.of("mock")); + Mockito.mockStatic(EventProcessor.class); + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(true); } @AfterAll @@ -85,4 +89,4 @@ static Stream onServiceExitCase() { arguments(mocker1, asserts2) ); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/test/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractorTest.java b/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/test/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractorTest.java index d37cad8bc..0a10e3bc8 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/test/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractorTest.java +++ b/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/test/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractorTest.java @@ -5,6 +5,8 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.context.RecordLimiter; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; +import io.arex.inst.runtime.util.CaseManager; import io.arex.inst.runtime.util.IgnoreUtils; import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcContextAttachment; @@ -43,6 +45,8 @@ static void setUp() { Mockito.mockStatic(Config.class); Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); Mockito.when(ContextManager.currentContext()).thenReturn(ArexContext.of("mock")); + Mockito.mockStatic(EventProcessor.class); + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(true); } @AfterAll @@ -123,4 +127,4 @@ static Stream onServiceExitCase() { arguments(mocker1, asserts2) ); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java b/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java index f4fb14cd0..48945c040 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java +++ b/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java @@ -4,6 +4,7 @@ import io.arex.agent.bootstrap.util.StringUtil; import io.arex.inst.runtime.config.Config; import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.model.ArexConstants; import io.arex.inst.runtime.util.IgnoreUtils; @@ -23,6 +24,9 @@ protected static Mocker buildMocker(Mocker mocker, AbstractAdapter adapter, Map< } protected static boolean shouldSkip(AbstractAdapter adapter) { + if (!EventProcessor.dependencyInitComplete()) { + return true; + } // exclude dubbo framework method if (EXCLUDE_DUBBO_METHOD_LIST.contains(adapter.getServiceOperation())) { return true; diff --git a/arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboExtractorTest.java b/arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboExtractorTest.java index 4ad82a9ee..2885156b1 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboExtractorTest.java +++ b/arex-instrumentation/dubbo/arex-dubbo-common/src/test/java/io/arex/inst/dubbo/common/DubboExtractorTest.java @@ -5,7 +5,9 @@ import io.arex.inst.runtime.config.Config; import io.arex.inst.runtime.context.ArexContext; import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.util.CaseManager; import io.arex.inst.runtime.util.IgnoreUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -34,6 +36,7 @@ static void setUp() { Mockito.mockStatic(Config.class); Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); Mockito.mockStatic(ContextManager.class); + Mockito.mockStatic(EventProcessor.class); } @AfterAll @@ -58,6 +61,7 @@ void shouldSkip(Runnable mocker, Predicate predicate) { } static Stream shouldSkipCase() { + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(true); Runnable mocker1 = () -> { adapter.setServiceOperation("org.apache.dubbo.metadata.MetadataService.getMetadataInfo"); }; @@ -80,6 +84,9 @@ static Stream shouldSkipCase() { Runnable mocker6 = () -> { Mockito.when(IgnoreUtils.excludeEntranceOperation(any())).thenReturn(false); }; + Runnable mocker7 = () -> { + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(false); + }; Predicate predicate1 = result -> result; Predicate predicate2 = result -> !result; return Stream.of( @@ -88,7 +95,8 @@ static Stream shouldSkipCase() { arguments(mocker3, predicate2), arguments(mocker4, predicate1), arguments(mocker5, predicate1), - arguments(mocker6, predicate2) + arguments(mocker6, predicate2), + arguments(mocker7, predicate1) ); } @@ -102,4 +110,4 @@ void setResponseHeader() { assertTrue(map.containsKey(ArexConstants.RECORD_ID)); assertTrue(map.containsKey(ArexConstants.REPLAY_ID)); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/dynamic/arex-cache/src/main/java/io/arex/inst/cache/spring/SpringCacheInstrumentation.java b/arex-instrumentation/dynamic/arex-cache/src/main/java/io/arex/inst/cache/spring/SpringCacheInstrumentation.java index 426a8e700..791a056a3 100644 --- a/arex-instrumentation/dynamic/arex-cache/src/main/java/io/arex/inst/cache/spring/SpringCacheInstrumentation.java +++ b/arex-instrumentation/dynamic/arex-cache/src/main/java/io/arex/inst/cache/spring/SpringCacheInstrumentation.java @@ -47,7 +47,7 @@ public static boolean onEnter(@Advice.Argument(2) Method method, return false; } - if (CacheLoaderUtil.needRecordOrReplay(method)) { + if (ContextManager.needRecordOrReplay() && CacheLoaderUtil.needRecordOrReplay(method)) { Cacheable cacheable = method.getDeclaredAnnotation(Cacheable.class); String keyExpression = cacheable != null ? cacheable.key() : null; extractor = new DynamicClassExtractor(method, args, keyExpression, null); diff --git a/arex-instrumentation/netty/arex-netty-v3/src/main/java/io/arex/inst/netty/v3/server/RequestTracingHandler.java b/arex-instrumentation/netty/arex-netty-v3/src/main/java/io/arex/inst/netty/v3/server/RequestTracingHandler.java index e6ccf9cb4..f66255fc5 100644 --- a/arex-instrumentation/netty/arex-netty-v3/src/main/java/io/arex/inst/netty/v3/server/RequestTracingHandler.java +++ b/arex-instrumentation/netty/arex-netty-v3/src/main/java/io/arex/inst/netty/v3/server/RequestTracingHandler.java @@ -9,6 +9,7 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.listener.CaseEvent; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.listener.EventSource; import io.arex.inst.runtime.log.LogManager; import io.arex.inst.runtime.model.ArexConstants; @@ -95,6 +96,9 @@ public void writeComplete(ChannelHandlerContext ctx, WriteCompletionEvent event) } private boolean shouldSkip(HttpRequest request, String caseId) { + if (!EventProcessor.dependencyInitComplete()) { + return true; + } // Replay scene if (StringUtil.isNotEmpty(caseId)) { return Config.get().getBoolean(ConfigConstants.DISABLE_REPLAY, false); @@ -117,4 +121,4 @@ private boolean shouldSkip(HttpRequest request, String caseId) { return Config.get().invalidRecord(request.getUri()); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/netty/arex-netty-v3/src/test/java/io/arex/inst/netty/v3/server/RequestTracingHandlerTest.java b/arex-instrumentation/netty/arex-netty-v3/src/test/java/io/arex/inst/netty/v3/server/RequestTracingHandlerTest.java index 4eba156aa..e08d19d3d 100644 --- a/arex-instrumentation/netty/arex-netty-v3/src/test/java/io/arex/inst/netty/v3/server/RequestTracingHandlerTest.java +++ b/arex-instrumentation/netty/arex-netty-v3/src/test/java/io/arex/inst/netty/v3/server/RequestTracingHandlerTest.java @@ -9,7 +9,9 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.context.RecordLimiter; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.util.CaseManager; import io.arex.inst.runtime.util.IgnoreUtils; import io.arex.inst.runtime.util.MockUtils; import org.jboss.netty.channel.ChannelHandlerContext; @@ -53,6 +55,8 @@ static void setUp() { Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); Mockito.mockStatic(MockUtils.class); Mockito.when(ContextManager.currentContext()).thenReturn(ArexContext.of("mock")); + Mockito.mockStatic(EventProcessor.class); + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(true); } @AfterAll @@ -109,6 +113,10 @@ static Stream channelReadCase() { Mockito.when(ContextManager.currentContext()).thenReturn(Mockito.mock(ArexContext.class)); }; + Runnable mocker8 = () -> { + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(false); + }; + return Stream.of( arguments(mocker1, event), arguments(mocker2, event), @@ -117,7 +125,8 @@ static Stream channelReadCase() { arguments(mocker4_1, event), arguments(mocker5, event), arguments(mocker6, event), - arguments(mocker7, event) + arguments(mocker7, event), + arguments(mocker8, event) ); } @@ -163,4 +172,4 @@ static Stream writeCompleteCase() { arguments(mocker4, asserts2) ); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/netty/arex-netty-v4/src/main/java/io/arex/inst/netty/v4/server/RequestTracingHandler.java b/arex-instrumentation/netty/arex-netty-v4/src/main/java/io/arex/inst/netty/v4/server/RequestTracingHandler.java index 3f2cbd492..947e1113f 100644 --- a/arex-instrumentation/netty/arex-netty-v4/src/main/java/io/arex/inst/netty/v4/server/RequestTracingHandler.java +++ b/arex-instrumentation/netty/arex-netty-v4/src/main/java/io/arex/inst/netty/v4/server/RequestTracingHandler.java @@ -7,6 +7,7 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.listener.CaseEvent; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.listener.EventSource; import io.arex.inst.runtime.log.LogManager; import io.arex.inst.runtime.model.ArexConstants; @@ -76,6 +77,9 @@ private void recordBody(ChannelHandlerContext ctx, HttpContent httpContent) { } private boolean shouldSkip(HttpRequest request, String caseId) { + if (!EventProcessor.dependencyInitComplete()) { + return true; + } // Replay scene if (StringUtil.isNotEmpty(caseId)) { return Config.get().getBoolean(ConfigConstants.DISABLE_REPLAY, false); @@ -119,4 +123,4 @@ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { super.channelReadComplete(ctx); } } -} \ No newline at end of file +} diff --git a/arex-instrumentation/netty/arex-netty-v4/src/test/java/io/arex/inst/netty/v4/server/RequestTracingHandlerTest.java b/arex-instrumentation/netty/arex-netty-v4/src/test/java/io/arex/inst/netty/v4/server/RequestTracingHandlerTest.java index f68cc3677..eb3275075 100644 --- a/arex-instrumentation/netty/arex-netty-v4/src/test/java/io/arex/inst/netty/v4/server/RequestTracingHandlerTest.java +++ b/arex-instrumentation/netty/arex-netty-v4/src/test/java/io/arex/inst/netty/v4/server/RequestTracingHandlerTest.java @@ -8,8 +8,10 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.context.RecordLimiter; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.model.ArexConstants; import io.arex.inst.netty.v4.common.NettyHelper; +import io.arex.inst.runtime.util.CaseManager; import io.arex.inst.runtime.util.IgnoreUtils; import io.arex.inst.runtime.util.MockUtils; import io.netty.buffer.EmptyByteBuf; @@ -59,6 +61,8 @@ static void setUp() { mockCaseEvent = Mockito.mockStatic(CaseEventDispatcher.class); Mockito.mockStatic(MockUtils.class); Mockito.when(ContextManager.currentContext()).thenReturn(ArexContext.of("mock")); + Mockito.mockStatic(EventProcessor.class); + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(true); } @AfterAll @@ -126,6 +130,9 @@ static Stream channelReadCase() { Runnable mocker9 = () -> { mocker.getTargetRequest().setBody("mock"); }; + Runnable mocker10 = () -> { + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(false); + }; return Stream.of( arguments(mocker1, request), @@ -137,7 +144,8 @@ static Stream channelReadCase() { arguments(mocker6, request), arguments(mocker7, httpContent), arguments(mocker8, httpContent), - arguments(mocker9, httpContent) + arguments(mocker9, httpContent), + arguments(mocker10, request) ); } @@ -182,4 +190,4 @@ static Stream channelReadCompleteCase() { arguments(mocker3, asserts2) ); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java index 4ea02032b..6db587ba0 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java @@ -9,6 +9,7 @@ import io.arex.inst.runtime.config.Config; import io.arex.inst.runtime.context.ArexContext; import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.request.RequestHandlerManager; import io.arex.inst.runtime.listener.CaseEvent; import io.arex.inst.runtime.listener.CaseEventDispatcher; @@ -205,6 +206,10 @@ public static void onInvokeForRequestExit( private static boolean shouldSkip(ServletAdapter adapter, TRequest httpServletRequest) { + if (!EventProcessor.dependencyInitComplete()) { + return true; + } + // skip if pre-request http-method is HEAD or OPTIONS if (HttpMethod.HEAD.matches(adapter.getMethod(httpServletRequest)) || HttpMethod.OPTIONS.matches(adapter.getMethod(httpServletRequest))) { diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java index d3233f292..3b766fe80 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/ServletAdviceHelperTest.java @@ -6,6 +6,7 @@ import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.context.RecordLimiter; import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventProcessor; import io.arex.inst.runtime.model.ArexConstants; import io.arex.inst.httpservlet.adapter.ServletAdapter; import io.arex.inst.runtime.util.IgnoreUtils; @@ -20,7 +21,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; import org.mockito.MockedConstruction; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -62,6 +62,8 @@ static void setUp() { Mockito.mockStatic(Config.class); Mockito.when(Config.get()).thenReturn(Mockito.mock(Config.class)); Mockito.when(adapter.getServletVersion()).thenReturn("mock"); + Mockito.mockStatic(EventProcessor.class); + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(true); } @AfterAll @@ -93,6 +95,7 @@ static Stream onServiceEnterCase() { Mockito.when(adapter.asHttpServletResponse(any())).thenReturn("mock"); Mockito.when(adapter.getAttribute(any(), any())).thenReturn(Boolean.TRUE); }; + Runnable shouldSkipPreRequest = () -> { Mockito.when(adapter.getAttribute(any(), any())).thenReturn(Boolean.FALSE); Mockito.when(adapter.getMethod(any())).thenReturn("OPTIONS"); @@ -194,6 +197,10 @@ static Stream onServiceEnterCase() { Mockito.when(IgnoreUtils.excludeOperation(any())).thenReturn(true); }; + Runnable shouldSkipInvalidCase = () -> { + Mockito.when(EventProcessor.dependencyInitComplete()).thenReturn(false); + }; + Predicate> predicate1 = Objects::isNull; Predicate> predicate2 = Objects::nonNull; return Stream.of( @@ -220,8 +227,9 @@ static Stream onServiceEnterCase() { arguments("shouldSkip: hit includeOperaions while contextPath is empty", shouldSkip10, predicate1), arguments("shouldSkip: hit includeOperaions while contextPath is not empty", shouldSkip11, predicate1), arguments("shouldSkip: hit excludeOperaions while contextPath is empty", shouldSkip12, predicate1), - arguments("shouldSkip: hit excludeOperaions while contextPath is not empty", shouldSkip13, predicate1) - ); + arguments("shouldSkip: hit excludeOperaions while contextPath is not empty", shouldSkip13, predicate1), + arguments("shouldSkip: CaseManager.invalid returns true", shouldSkipInvalidCase, predicate1) + ); } @ParameterizedTest(name = "[{index}] {0}") From 597031dca4641d2e9eff3c2ea92164a46c2822e0 Mon Sep 17 00:00:00 2001 From: wangjie Date: Thu, 16 May 2024 10:58:09 +0800 Subject: [PATCH 05/20] fix: ArrayUtils.toString NPE caused by null array element (#482) * fix: dynamicClass method contain null param * fix ut * fix review * fix ut --------- Co-authored-by: mr3 --- .../arex/agent/bootstrap/util/ArrayUtils.java | 6 +++++- .../agent/bootstrap/util/ArrayUtilsTest.java | 20 +++++++++++-------- .../io/arex/inst/runtime/util/TypeUtil.java | 13 +----------- .../arex/inst/runtime/util/TypeUtilTest.java | 2 +- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/ArrayUtils.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/ArrayUtils.java index d7d3038ea..93277fdd2 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/ArrayUtils.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/ArrayUtils.java @@ -40,7 +40,11 @@ public static String toString(Object[] array, Function parser) { StringBuilder builder = new StringBuilder(); builder.append("[\""); for (int i = 0; ; i++) { - builder.append(parser != null ? parser.apply(array[i]) : array[i]); + if (array[i] == null) { + builder.append("null"); + } else { + builder.append(parser != null ? parser.apply(array[i]) : array[i]); + } if (i == iMax) { return builder.append("\"]").toString(); } diff --git a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/ArrayUtilsTest.java b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/ArrayUtilsTest.java index c4376b9a2..3c130ba94 100644 --- a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/ArrayUtilsTest.java +++ b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/ArrayUtilsTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import java.util.function.Predicate; import java.util.stream.Stream; @@ -35,17 +34,22 @@ static Stream addAllCase() { @Test void isEmpty() { assertTrue(ArrayUtils.isEmpty(new Object[0])); + assertFalse(ArrayUtils.isNotEmpty(new Object[0])); } @ParameterizedTest - @CsvSource(value = { - ", null", - "mock1_mock2, '[\"mock1\", \"mock2\"]'" - }, nullValues = {"null"}) - void isNotEmpty(String arrayStr, String expect) { - Object[] array = arrayStr == null ? null : arrayStr.split("_"); + @MethodSource("ArrayUtilsToStringCase") + void toString(Object[] array, String expect) { assertEquals(expect, ArrayUtils.toString(array, Object::toString)); } + static Stream ArrayUtilsToStringCase() { + return Stream.of( + arguments(new Object[0], null), + arguments(new Object[]{null}, "[\"null\"]"), + arguments(new Object[]{"paramString", null}, "[\"paramString\", \"null\"]") + ); + } + -} \ No newline at end of file +} diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java index d42dc4692..52ea4f94e 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java @@ -383,20 +383,9 @@ private static String parameterizedTypeToString(ParameterizedType type) { return builder.toString(); } - public static String arrayObjectToString(Object[] objects) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - builder.append(objects[i].getClass().getName()); - if (i != objects.length - 1) { - builder.append(","); - } - } - return builder.toString(); - } - public static String errorSerializeToString(Object object) { if (object instanceof Object[]) { - return arrayObjectToString((Object[]) object); + return ArrayUtils.toString((Object[]) object, o -> o.getClass().getTypeName()); } return getName(object); } diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java index 492fc3859..082a81a9b 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java @@ -258,7 +258,7 @@ void testSerializeObjectToString() { args[1] = arg2; args[2] = arg3; String argsType = TypeUtil.errorSerializeToString(args); - assertEquals("java.lang.String,java.lang.Double,java.time.LocalDateTime", argsType); + assertEquals("[\"java.lang.String\", \"java.lang.Double\", \"java.time.LocalDateTime\"]", argsType); // just one class final String arg2Type = TypeUtil.errorSerializeToString(arg2); assertEquals("java.lang.Double", arg2Type); From b3ad6ed8eaf754da32762d501656223df7621ba6 Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Fri, 17 May 2024 11:51:46 +0800 Subject: [PATCH 06/20] fix: nested collection determination error (#483) --- .../main/java/io/arex/inst/runtime/util/TypeUtil.java | 9 +++------ .../io/arex/inst/runtime/serializer/SerializerTest.java | 6 ++++++ .../java/io/arex/inst/runtime/util/TypeUtilTest.java | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java index 52ea4f94e..53554bdfa 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/TypeUtil.java @@ -455,15 +455,12 @@ public static Collection> toNestedCollection(Object object) { } for (Object innerCollection : collection) { - if (innerCollection == null) { - continue; - } - if (!(innerCollection instanceof Collection)) { - return null; + if (innerCollection instanceof Collection) { + return collection; } } - return collection; + return null; } public static boolean isProtobufClass(Class clazz) { diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java index bdc59d0b7..965636b02 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/serializer/SerializerTest.java @@ -77,6 +77,12 @@ void testNestedList() { nestedList2.add("nestList2-2"); list.add(null); + String nullListJson = Serializer.serialize(list, "jackson"); + assertEquals("[null]", nullListJson); + String nullListTypeName = TypeUtil.getName(list); + Object deserialize = Serializer.deserialize(nullListJson, nullListTypeName); + assertEquals(list, deserialize); + list.add(new ArrayList<>()); list.add(nestedList1); list.add(nestedList2); diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java index 082a81a9b..8930a6a79 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/TypeUtilTest.java @@ -328,9 +328,10 @@ void testToNestedCollection() { actualResult = TypeUtil.toNestedCollection(collection); assertNull(actualResult); + collection.add(null); collection.add(null); actualResult = TypeUtil.toNestedCollection(collection); - assertEquals(collection, actualResult); + assertNull(actualResult); collection.add(new ArrayList<>()); actualResult = TypeUtil.toNestedCollection(collection); From 863843b462e77d697ea3c2e8ddab30846c9d4fa9 Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Wed, 29 May 2024 13:43:13 +0800 Subject: [PATCH 07/20] fix: mybatis non-standard key properties (#489) --- .../io/arex/inst/database/mybatis3/InternalExecutor.java | 2 ++ .../inst/database/mybatis3/InternalExecutorTest.java | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java b/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java index 08cd00e52..9838db737 100644 --- a/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java +++ b/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java @@ -201,6 +201,8 @@ private static String[] transformerProperties(String[] keyProperties) { int firstDot = keyProperties[i].indexOf("."); if (firstDot != -1) { primaryKeyNames[i] = StringUtil.substring(keyProperties[i], firstDot + 1); + } else { + primaryKeyNames[i] = keyProperties[i]; } } return primaryKeyNames; diff --git a/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java b/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java index e009012b0..b5e1f12aa 100644 --- a/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java +++ b/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java @@ -219,6 +219,15 @@ void testSaveKeyHolder() { target.record(extractor1, mappedStatement1, parameterObject, 1, null); Mockito.verify(extractor1, Mockito.times(1)).setKeyHolder("100120,java.lang.String;name,java.lang.String"); Mockito.verify(extractor1, Mockito.times(1)).setKeyHolderName("id;name"); + + // parameterObject is ParamMap + MappedStatement mappedStatement2 = Mockito.mock(MappedStatement.class); + Mockito.when(mappedStatement2.getKeyProperties()).thenReturn(new String[]{"id"}); + MapperMethod.ParamMap paramMap = new MapperMethod.ParamMap<>(); + parameterObject.setId("test1"); + paramMap.put("id", parameterObject); + target.record(extractor1, mappedStatement2, paramMap, 1, null); + Mockito.verify(extractor1, Mockito.times(1)).setKeyHolder("test1,java.lang.String"); } @Test From ccefcb9430d5a64f7247718a59412c2ffe881c0b Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Thu, 30 May 2024 11:58:22 +0800 Subject: [PATCH 08/20] feat: support mybatis plus insertBatchSomeColumn primary key (#493) --- .../database/mybatis3/InternalExecutor.java | 57 +++++++++++++++---- .../mybatis3/InternalExecutorTest.java | 25 ++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java b/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java index 9838db737..081c30af0 100644 --- a/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java +++ b/arex-instrumentation/database/arex-database-mybatis3/src/main/java/io/arex/inst/database/mybatis3/InternalExecutor.java @@ -15,12 +15,14 @@ import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.reflection.Reflector; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class InternalExecutor { private static final Map REFLECTOR_MAP = new ConcurrentHashMap<>(); private static final char KEYHOLDER_SEPARATOR = ';'; + private static final char LIST_KEYHOLDER_SEPARATOR = '@'; private static final char KEYHOLDER_TYPE_SEPARATOR = ','; private static boolean isIPageLoaded = false; static { @@ -104,28 +106,40 @@ private static void restorePage(DatabaseExtractor extractor, Object paramete } private static void restoreKeyHolder(MappedStatement ms, DatabaseExtractor executor, Object o) { - String[] keyHolderList = StringUtil.split(executor.getKeyHolder(), KEYHOLDER_SEPARATOR); - if (ArrayUtils.isEmpty(keyHolderList)) { + String recordKeyHolder = executor.getKeyHolder(); + if (StringUtil.isEmpty(recordKeyHolder)) { return; } - Object insertEntity = o instanceof ParamMap ? getEntityFromMap((ParamMap) o) : o; + String[] keyHolderList = StringUtil.split(recordKeyHolder, LIST_KEYHOLDER_SEPARATOR); String[] keyProperties = getPrimaryKeyNames(ms, o, executor); + if (insertEntity instanceof List) { + List list = (List) insertEntity; + for (int i = 0; i < list.size(); i++) { + Object entity = list.get(i); + restoreKeyHolderForEntity(entity, keyHolderList[i], keyProperties); + } + } else { + restoreKeyHolderForEntity(insertEntity, recordKeyHolder, keyProperties); + } + + } - if (ArrayUtils.isEmpty(keyProperties) || keyProperties.length != keyHolderList.length || insertEntity == null) { + private static void restoreKeyHolderForEntity(Object insertEntity, String entityKeyHolder, String[] keyProperties) { + String[] multiKeyHolder = StringUtil.split(entityKeyHolder, KEYHOLDER_SEPARATOR); + if (insertEntity == null || ArrayUtils.isEmpty(multiKeyHolder) || ArrayUtils.isEmpty(keyProperties)) { return; } - try { Class entityClass = insertEntity.getClass(); Reflector reflector = REFLECTOR_MAP.computeIfAbsent(entityClass.getName(), className -> new Reflector(entityClass)); - for (int i = 0; i < keyHolderList.length; i++) { - String[] valueType = StringUtil.split(keyHolderList[i], KEYHOLDER_TYPE_SEPARATOR); + for (int i = 0; i < multiKeyHolder.length; i++) { + String[] valueType = StringUtil.split(multiKeyHolder[i], KEYHOLDER_TYPE_SEPARATOR); Object keyHolderValue = Serializer.deserialize(valueType[0], valueType[1]); reflector.getSetInvoker(keyProperties[i]).invoke(insertEntity, new Object[]{keyHolderValue}); } - } catch (Exception ignored) {} + }catch (Exception ignored) {} } private static void saveKeyHolder(MappedStatement ms, DatabaseExtractor executor, Object o) { @@ -141,6 +155,28 @@ private static void saveKeyHolder(MappedStatement ms, DatabaseExtractor executor StringBuilder builder = new StringBuilder(); StringBuilder keyHolderNameBuilder = new StringBuilder(); + + if (insertEntity instanceof List) { + List list = (List) insertEntity; + for (int i = 0; i < list.size(); i++) { + Object entity = list.get(i); + if (entity == null) { + continue; + } + buildKeyHolderForEntity(entity, primaryKeyNames, builder, keyHolderNameBuilder); + if (i < list.size() - 1) { + keyHolderNameBuilder.append(LIST_KEYHOLDER_SEPARATOR); + builder.append(LIST_KEYHOLDER_SEPARATOR); + } + } + } else { + buildKeyHolderForEntity(insertEntity, primaryKeyNames, builder, keyHolderNameBuilder); + } + executor.setKeyHolderName(keyHolderNameBuilder.toString()); + executor.setKeyHolder(builder.toString()); + } + + private static void buildKeyHolderForEntity(Object insertEntity, String[] primaryKeyNames, StringBuilder builder, StringBuilder keyHolderNameBuilder) { Class entityClass = insertEntity.getClass(); Reflector reflector = REFLECTOR_MAP.computeIfAbsent(entityClass.getName(), className -> new Reflector(entityClass)); @@ -160,8 +196,6 @@ private static void saveKeyHolder(MappedStatement ms, DatabaseExtractor executor } catch (Exception ignored) { } } - executor.setKeyHolderName(keyHolderNameBuilder.toString()); - executor.setKeyHolder(builder.toString()); } private static String[] getPrimaryKeyNames(MappedStatement ms, Object o) { @@ -180,7 +214,8 @@ private static String[] getPrimaryKeyNames(MappedStatement ms, Object o) { private static String[] getPrimaryKeyNames(MappedStatement ms, Object o, DatabaseExtractor extractor) { if (extractor != null && StringUtil.isNotEmpty(extractor.getKeyHolderName())) { - return StringUtil.split(extractor.getKeyHolderName(), KEYHOLDER_SEPARATOR); + String[] keyHolderNameList = StringUtil.split(extractor.getKeyHolderName(), LIST_KEYHOLDER_SEPARATOR); + return StringUtil.split(keyHolderNameList[0], KEYHOLDER_SEPARATOR); } String[] keyProperties = ms.getKeyProperties(); diff --git a/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java b/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java index b5e1f12aa..8b5a70683 100644 --- a/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java +++ b/arex-instrumentation/database/arex-database-mybatis3/src/test/java/io/arex/inst/database/mybatis3/InternalExecutorTest.java @@ -21,6 +21,7 @@ import java.lang.reflect.Type; import java.sql.SQLException; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; @@ -228,6 +229,17 @@ void testSaveKeyHolder() { paramMap.put("id", parameterObject); target.record(extractor1, mappedStatement2, paramMap, 1, null); Mockito.verify(extractor1, Mockito.times(1)).setKeyHolder("test1,java.lang.String"); + + // parameterObject is List + MappedStatement mappedStatement3 = Mockito.mock(MappedStatement.class); + Mockito.when(mappedStatement3.getKeyProperties()).thenReturn(new String[]{"id"}); + Entity parameterObject1 = new Entity(); + parameterObject1.setId("testId1"); + Entity parameterObject2 = new Entity(); + parameterObject2.setId("testId2"); + target.record(extractor1, mappedStatement3, Arrays.asList(parameterObject1, parameterObject2), 1, null); + Mockito.verify(extractor1, Mockito.times(1)).setKeyHolder("testId1,java.lang.String@testId2,java.lang.String"); + Mockito.verify(extractor1, Mockito.times(1)).setKeyHolderName("id@id"); } @Test @@ -261,6 +273,19 @@ void testRestoreKeyHolder() { target.replay(extractor1, mappedStatement1, mockEntity); Mockito.verify(mockEntity, Mockito.times(2)).setId("100120"); Mockito.verify(mockEntity, Mockito.times(1)).setName("name"); + + // parameterObject is ParamMap and value is List + MapperMethod.ParamMap> paramMap = new MapperMethod.ParamMap<>(); + Entity parameterObject1 = new Entity(); + Entity parameterObject2 = new Entity(); + paramMap.put("id", Arrays.asList(parameterObject1, parameterObject2)); + Mockito.when(extractor1.getKeyHolder()).thenReturn("testId1,java.lang.String@testId2,java.lang.String"); + Mockito.when(extractor1.getKeyHolderName()).thenReturn("id@id"); + Mockito.when(Serializer.deserialize("testId1", "java.lang.String")).thenReturn("testId1"); + Mockito.when(Serializer.deserialize("testId2", "java.lang.String")).thenReturn("testId2"); + target.replay(extractor1, mappedStatement1, paramMap); + assertEquals("testId1", parameterObject1.getId()); + assertEquals("testId2", parameterObject2.getId()); } static class Entity { From db8992e4ce0f6f4476c2d22239c823e2eda9754a Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:48:42 +0800 Subject: [PATCH 09/20] feat: support mongo get more (#494) --- .../inst/runtime/context/ArexContext.java | 10 ++- .../arex/inst/database/mongo/MongoHelper.java | 86 ++++++++++++++++++- .../QueryBatchCursorInstrumentation.java | 39 ++++++++- .../inst/database/mongo/MongoHelperTest.java | 77 ++++++++++++++++- pom.xml | 2 +- 5 files changed, 203 insertions(+), 11 deletions(-) diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ArexContext.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ArexContext.java index 1b3899ed3..e5eca10c0 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ArexContext.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ArexContext.java @@ -96,7 +96,7 @@ public void setAttachment(String key, Object value) { } if (attachments == null) { - attachments = new HashMap<>(); + attachments = new ConcurrentHashMap<>(); } attachments.put(key, value); @@ -110,6 +110,14 @@ public Object getAttachment(String key) { return attachments.get(key); } + public Object removeAttachment(String key) { + if (attachments == null) { + return null; + } + + return attachments.remove(key); + } + public boolean isRedirectRequest() { return isRedirectRequest; } diff --git a/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java b/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java index ada21ea1e..6287a228b 100644 --- a/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java +++ b/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/MongoHelper.java @@ -4,17 +4,18 @@ import io.arex.agent.bootstrap.model.MockResult; import io.arex.agent.bootstrap.util.CollectionUtil; +import io.arex.agent.bootstrap.util.MapUtils; import io.arex.agent.bootstrap.util.StringUtil; import io.arex.inst.database.common.DatabaseExtractor; import io.arex.inst.database.mongo.wrapper.ResultWrapper; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.log.LogManager; import io.arex.inst.runtime.serializer.Serializer; import io.arex.inst.runtime.util.TypeUtil; import org.bson.Document; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.lang.reflect.Field; +import java.util.*; import static io.arex.inst.runtime.model.ArexConstants.GSON_REQUEST_SERIALIZER; import static io.arex.inst.runtime.model.ArexConstants.GSON_SERIALIZER; @@ -26,6 +27,14 @@ public class MongoHelper { private static final String ID_FIELD = "_id"; private static final Set INSERT_METHODS = CollectionUtil.newHashSet("executeInsertOne", "executeInsertMany"); + private static final Set QUERY_BATCH_CURSOR_CLASSES = CollectionUtil.newHashSet("com.mongodb.internal.operation.QueryBatchCursor", "com.mongodb.operation.QueryBatchCursor"); + private static final String FIND_OPERATION = "FindOperation"; + private static final Field NEX_BATCH_FIELD = getNexBatchField(); + private static final String RESULT = "result"; + private static final String NEXT_BATCH_LIST = "nextBatchList"; + private static final String DATABASE_EXTRACTOR = "databaseExtractor"; + private static final String MONGO_CURSOR = "mongoCursor_"; + /** * used to separate keyHolder and keyHolderType */ @@ -35,6 +44,25 @@ private MongoHelper() { static { Serializer.getINSTANCE().getSerializer(GSON_SERIALIZER).addMapSerializer(Document.class); } + + /** + * achieving compatibility with different versions of Mongo. + * after 4.xx, com.mongodb.internal.operation.QueryBatchCursor + * before 4.xx, com.mongodb.operation.QueryBatchCursor + */ + private static Field getNexBatchField() { + for (String queryBatchCursorClass : QUERY_BATCH_CURSOR_CLASSES) { + try { + Field nextBatch = Class.forName(queryBatchCursorClass).getDeclaredField("nextBatch"); + nextBatch.setAccessible(true); + return nextBatch; + } catch (Exception ignore) { + LogManager.info("getNexBatchField", StringUtil.format("load %s false", queryBatchCursorClass)); + } + } + return null; + } + public static MockResult replay(String methodName, MongoNamespace namespace, Object filter) { String dbName = namespace.getFullName(); String parameter = Serializer.serialize(filter, GSON_REQUEST_SERIALIZER); @@ -65,10 +93,60 @@ public static void record(String methodName, MongoNamespace namespace, Object fi saveKeyHolder(extractor, filter); } + /* + * If the Find method is used, there is a possibility of calling the getMore method. + * In this case, we will not record immediately, but instead save the relevant parameters. + * Only when the hasNext method of the queryCursor returns false or the close method is called, + * will the recordFindOperation operation be triggered. This is when all the final queried data can be obtained. + */ + if (FIND_OPERATION.equals(methodName) && result != null) { + Map map = new HashMap<>(3); + map.put(DATABASE_EXTRACTOR, extractor); + map.put(RESULT, result); + map.put(NEXT_BATCH_LIST, new ArrayList<>()); + ContextManager.currentContext().setAttachment(MONGO_CURSOR + result.hashCode(), map); + return; + } + Object actualResult = ResultWrapper.wrap(result); extractor.recordDb(actualResult, GSON_SERIALIZER); } + public static void recordFindOperation(int queryCursorHashCode) { + try { + Map map = (Map) ContextManager.currentContext().removeAttachment(MONGO_CURSOR + queryCursorHashCode); + if (MapUtils.isEmpty(map)) { + return; + } + List nextBatchList = (List) map.get(NEXT_BATCH_LIST); + Object result = map.get(RESULT); + DatabaseExtractor extractor = (DatabaseExtractor) map.get(DATABASE_EXTRACTOR); + Class resultClass = result.getClass(); + if (CollectionUtil.isEmpty(nextBatchList)) { + extractor.recordDb(result, GSON_SERIALIZER); + return; + } + if (QUERY_BATCH_CURSOR_CLASSES.contains(resultClass.getName()) && NEX_BATCH_FIELD != null) { + NEX_BATCH_FIELD.set(result, nextBatchList); + extractor.recordDb(result, GSON_SERIALIZER); + } + } catch (Exception e) { + LogManager.warn("recordFindOperation", e); + } + } + + public static void addNextBatchList(int queryCursorHashCode, List nextBatchList) { + try { + Map map = (Map) ContextManager.currentContext().getAttachment(MONGO_CURSOR + queryCursorHashCode); + if (MapUtils.isEmpty(map)) { + return; + } + List list = (List) map.get(NEXT_BATCH_LIST); + list.addAll(nextBatchList); + } catch (Exception e) { + LogManager.warn("addNextBatchList", e); + } + } private static void restoreKeyHolder(String keyHolder, Object filter) { if (StringUtil.isEmpty(keyHolder)) { diff --git a/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/QueryBatchCursorInstrumentation.java b/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/QueryBatchCursorInstrumentation.java index de3e85cf8..5ed59eccb 100644 --- a/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/QueryBatchCursorInstrumentation.java +++ b/arex-instrumentation/database/arex-database-mongo/src/main/java/io/arex/inst/database/mongo/QueryBatchCursorInstrumentation.java @@ -7,7 +7,7 @@ import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import static net.bytebuddy.matcher.ElementMatchers.*; @@ -18,15 +18,48 @@ protected ElementMatcher typeMatcher() { return namedOneOf("com.mongodb.internal.operation.QueryBatchCursor", "com.mongodb.operation.QueryBatchCursor"); } + /** + * query process: + * 1. for each -> hasNext -> next, if hasNext return false, indicates that all data has been returned. + * 2. first -> hasNext -> next, get the first data, then close the cursor. + */ @Override public List methodAdvices() { - return Collections.singletonList(new MethodInstrumentation(isMethod().and(named("close")), SkipAdvice.class.getName())); + return Arrays.asList(new MethodInstrumentation(isMethod().and(named("close")), CloseAdvice.class.getName()), + new MethodInstrumentation(isMethod().and(named("hasNext")), HashNextAdvice.class.getName()), + new MethodInstrumentation(isMethod().and(named("next")), NextAdvice.class.getName())); } - public static class SkipAdvice { + public static class CloseAdvice { @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, suppress = Throwable.class) public static boolean onEnter() { return ContextManager.needReplay(); } + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.This Object thiz) { + if (ContextManager.needRecord()) { + MongoHelper.recordFindOperation(thiz.hashCode()); + } + } + } + + public static class HashNextAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.This Object thiz, + @Advice.Return boolean hasNext) { + if (ContextManager.needRecord() && !hasNext) { + MongoHelper.recordFindOperation(thiz.hashCode()); + } + } + } + + public static class NextAdvice { + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.This Object thiz, + @Advice.Return List next) { + if (ContextManager.needRecord()) { + MongoHelper.addNextBatchList(thiz.hashCode(), next); + } + } } } diff --git a/arex-instrumentation/database/arex-database-mongo/src/test/java/io/arex/inst/database/mongo/MongoHelperTest.java b/arex-instrumentation/database/arex-database-mongo/src/test/java/io/arex/inst/database/mongo/MongoHelperTest.java index 03f653d75..8e08046e2 100644 --- a/arex-instrumentation/database/arex-database-mongo/src/test/java/io/arex/inst/database/mongo/MongoHelperTest.java +++ b/arex-instrumentation/database/arex-database-mongo/src/test/java/io/arex/inst/database/mongo/MongoHelperTest.java @@ -7,12 +7,14 @@ import io.arex.agent.bootstrap.model.MockResult; import io.arex.inst.database.common.DatabaseExtractor; import io.arex.inst.database.mongo.wrapper.ResultWrapper; +import io.arex.inst.runtime.context.ArexContext; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.model.ArexConstants; import io.arex.inst.runtime.serializer.Serializer; import io.arex.inst.runtime.serializer.Serializer.Builder; import io.arex.inst.runtime.serializer.StringSerializable; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import io.arex.inst.runtime.util.TypeUtil; import org.bson.Document; @@ -32,6 +34,7 @@ static void setUp() { final Builder builder = Serializer.builder(serializable); builder.addSerializer("gson", serializable); builder.build(); + Mockito.mockStatic(ContextManager.class); } @AfterAll @@ -41,6 +44,7 @@ static void tearDown() throws Exception { instance.setAccessible(true); instance.set(null, null); serializable = null; + Mockito.clearAllCaches(); } @Test @@ -105,7 +109,76 @@ void record() { // no Document MongoHelper.record("executeInsertMany", Mockito.mock(MongoNamespace.class), "notDocument", "test", null); Mockito.verify(construction.constructed().get(3), Mockito.never()).setKeyHolder(anyString()); + // find operation + ArexContext testFind = ArexContext.of("testFind"); + Mockito.when(ContextManager.currentContext()).thenReturn(testFind); + MongoHelper.record("FindOperation", Mockito.mock(MongoNamespace.class), null, "findResult", null); + String resultHashCode = String.valueOf("findResult".hashCode()); + Object attachment = testFind.getAttachment("mongoCursor_" + resultHashCode); + assertTrue(attachment instanceof Map); + assertEquals("findResult", ((Map) attachment).get("result")); } + } + + @Test + void recordFindOperation() throws ClassNotFoundException { + int queryCursorHashCode = 123; + DatabaseExtractor mockExtractor = Mockito.mock(DatabaseExtractor.class); + Class queryBatchCursor = Class.forName("com.mongodb.internal.operation.QueryBatchCursor"); + Object queryBatchCursorInstance = Mockito.mock(queryBatchCursor); + List nextBatchList = Arrays.asList("item1", "item2", "item3"); + Map map = new HashMap<>(); + List list = new ArrayList<>(); + + ArexContext context = ArexContext.of("test"); + Mockito.when(ContextManager.currentContext()).thenReturn(context); + // map is null + MongoHelper.recordFindOperation(queryCursorHashCode); + Mockito.verify(mockExtractor, Mockito.never()).recordDb(nextBatchList, ArexConstants.GSON_SERIALIZER); + + // error + map.put("nextBatchList", nextBatchList); + map.put("databaseExtractor", mockExtractor); + context.setAttachment("mongoCursor_" + queryCursorHashCode, map); + assertDoesNotThrow(() -> MongoHelper.recordFindOperation(queryCursorHashCode)); + // normal + map.put("result", queryBatchCursorInstance); + context.setAttachment("mongoCursor_" + queryCursorHashCode, map); + MongoHelper.recordFindOperation(queryCursorHashCode); + Mockito.verify(mockExtractor, Mockito.times(1)).recordDb(queryBatchCursorInstance, ArexConstants.GSON_SERIALIZER); + } + + @Test + void addNextBatchList() { + // Prepare + int queryCursorHashCode = 123; + List nextBatchList = Arrays.asList("item1", "item2", "item3"); + Map map = new HashMap<>(); + List list = new ArrayList<>(); + + ArexContext context = ArexContext.of("test"); + Mockito.when(ContextManager.currentContext()).thenReturn(context); + + // map is empty + context.setAttachment(String.valueOf(queryCursorHashCode), map); + assertDoesNotThrow(() -> MongoHelper.addNextBatchList(queryCursorHashCode, nextBatchList)); + + // map is not empty + map.put("nextBatchList", list); + context.setAttachment("mongoCursor_" + queryCursorHashCode, map); + // Execute + MongoHelper.addNextBatchList(queryCursorHashCode, nextBatchList); + + // Verify + assertEquals(nextBatchList, list); + + // Mock + context.setAttachment("mongoCursor_" + queryCursorHashCode, "invalid"); + + // Execute + assertDoesNotThrow(() -> MongoHelper.addNextBatchList(queryCursorHashCode, nextBatchList)); + // Clean up + context.removeAttachment(String.valueOf(queryCursorHashCode)); } } diff --git a/pom.xml b/pom.xml index e413a771f..b3ff788a6 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ - 0.4.4 + 0.4.6 UTF-8 UTF-8 From b5d2da84601e030b63ff59c93c3296d962124e61 Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 5 Jun 2024 10:58:46 +0800 Subject: [PATCH 10/20] feat: support sql parse (#495) * feat: support sql parse * fix: sonar --- .../inst/runtime/model/ArexConstants.java | 3 ++ .../arex/inst/runtime/util/DatabaseUtils.java | 48 +++++++++++++++++++ .../inst/runtime/util/DatabaseUtilsTest.java | 20 ++++++++ .../database/common/DatabaseExtractor.java | 11 +++-- arex-third-party/pom.xml | 42 ++++++++++++++++ .../util/sqlparser/JSqlParserUtil.java | 44 +++++++++++++++++ .../util/sqlparser/TableSchema.java | 39 +++++++++++++++ 7 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java create mode 100644 arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java create mode 100644 arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/JSqlParserUtil.java create mode 100644 arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/TableSchema.java diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java index b9e3dc5eb..b90c9b6d4 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java @@ -61,4 +61,7 @@ private ArexConstants() {} public static final String RECORD_SIZE_LIMIT = "arex.record.size.limit"; public static final String SERVLET_V3 = "ServletV3"; public static final String SERVLET_V5 = "ServletV5"; + public static final String DATABASE = "database"; + public static final String DB_NAME = "dbName"; + public static final String DB_PARAMETERS = "parameters"; } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java new file mode 100644 index 000000000..220a2fc7f --- /dev/null +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java @@ -0,0 +1,48 @@ +package io.arex.inst.runtime.util; + +import io.arex.agent.bootstrap.util.StringUtil; +import io.arex.agent.thirdparty.util.sqlparser.JSqlParserUtil; +import io.arex.agent.thirdparty.util.sqlparser.TableSchema; + +import java.util.ArrayList; +import java.util.List; + +public class DatabaseUtils { + + private static final String DELIMITER = "@"; + + /** + * eg: db1@table1,table2@select@operation1;db2@table3,table4@select@operation2; + */ + public static String regenerateOperationName(String dbName, String operationName, String sqlText) { + // If the operation name already contains @, it means that it has already been generated and does not need to be generated again + if (StringUtil.isEmpty(sqlText) || StringUtil.isEmpty(operationName) || operationName.contains(DELIMITER)) { + return operationName; + } + + String[] sqls = StringUtil.split(sqlText, ';'); + List operationNames = new ArrayList<>(sqls.length); + for (String sql : sqls) { + if (StringUtil.isEmpty(sql)) { + continue; + } + try{ + TableSchema tableSchema = JSqlParserUtil.parse(sql); + tableSchema.setDbName(dbName); + operationNames.add(regenerateOperationName(tableSchema, operationName)); + } catch (Exception e) { + // ignore + } + } + return StringUtil.join(operationNames, ";"); + } + + private static String regenerateOperationName(TableSchema tableSchema, String originOperationName) { + return new StringBuilder(100).append(StringUtil.defaultString(tableSchema.getDbName())).append(DELIMITER) + .append(StringUtil.defaultString(StringUtil.join(tableSchema.getTableNames(), ","))).append(DELIMITER) + .append(StringUtil.defaultString(tableSchema.getAction())).append(DELIMITER) + .append(originOperationName) + .toString(); + } +} + diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java new file mode 100644 index 000000000..ae8db1521 --- /dev/null +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java @@ -0,0 +1,20 @@ +package io.arex.inst.runtime.util; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.*; + +class DatabaseUtilsTest { + + @ParameterizedTest + @CsvSource(value ={ + "database1, @, select * from table1, @", + "database1, query, ;, ''", + "database1, query, mock, ''", + "database1, query, select * from table1, database1@table1@Select@query" + }, nullValues={"null"}) + void regenerateOperationName(String dbName, String operationName, String sqlText, String expect) { + assertEquals(expect, DatabaseUtils.regenerateOperationName(dbName, operationName, sqlText)); + } +} \ No newline at end of file diff --git a/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java b/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java index 2747c10da..1c546b670 100644 --- a/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java +++ b/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java @@ -6,6 +6,7 @@ import io.arex.inst.runtime.model.ArexConstants; import io.arex.inst.runtime.serializer.Serializer; +import io.arex.inst.runtime.util.DatabaseUtils; import io.arex.inst.runtime.util.IgnoreUtils; import io.arex.inst.runtime.util.MockUtils; import io.arex.inst.runtime.util.TypeUtil; @@ -92,8 +93,10 @@ public MockResult replay() { } public MockResult replay(String serializer) { - // update after all dal components have obtained the real dbName(temporary solution) - boolean ignoreMockResult = IgnoreUtils.ignoreMockResult("database", methodName); + String serviceKey = StringUtil.defaultIfEmpty(this.dbName, ArexConstants.DATABASE); + String operationKey = DatabaseUtils.regenerateOperationName(dbName, this.methodName, this.sql); + boolean ignoreMockResult = IgnoreUtils.ignoreMockResult(serviceKey, operationKey); + Mocker replayMocker = MockUtils.replayMocker(makeMocker(null, serializer)); Object replayResult = null; if (MockUtils.checkResponseMocker(replayMocker)) { @@ -118,8 +121,8 @@ public MockResult replay(String serializer) { private Mocker makeMocker(Object response, String serializer) { Mocker mocker = MockUtils.createDatabase(this.methodName); mocker.getTargetRequest().setBody(this.sql); - mocker.getTargetRequest().setAttribute("dbName", this.dbName); - mocker.getTargetRequest().setAttribute("parameters", this.parameters); + mocker.getTargetRequest().setAttribute(ArexConstants.DB_NAME, this.dbName); + mocker.getTargetRequest().setAttribute(ArexConstants.DB_PARAMETERS, this.parameters); for (Map.Entry entry : extendFields.entrySet()) { mocker.getTargetResponse().setAttribute(entry.getKey(), entry.getValue()); } diff --git a/arex-third-party/pom.xml b/arex-third-party/pom.xml index 0f89cec99..a3f06e35e 100644 --- a/arex-third-party/pom.xml +++ b/arex-third-party/pom.xml @@ -10,4 +10,46 @@ 4.0.0 arex-third-party + + + + com.github.jsqlparser + jsqlparser + 4.9 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + + + package + + shade + + + + + net.sf.jsqlparser + io.arex.net.sf.jsqlparser + + + + + + + + + com.github.jsqlparser:jsqlparser + io.arex:arex-third-party + + + + + + \ No newline at end of file diff --git a/arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/JSqlParserUtil.java b/arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/JSqlParserUtil.java new file mode 100644 index 000000000..ce6061418 --- /dev/null +++ b/arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/JSqlParserUtil.java @@ -0,0 +1,44 @@ +package io.arex.agent.thirdparty.util.sqlparser; + +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.util.TablesNamesFinder; + +import java.util.*; +import java.util.regex.Pattern; + +public class JSqlParserUtil { + + private static final Pattern PATTERN = Pattern.compile("(\\s+|\"\\?\")"); + + /** + * parse table and action from sql + * @param sql sql + * @return table schema info + */ + public static TableSchema parse(String sql) throws Exception { + sql = PATTERN.matcher(sql).replaceAll(" "); + + Statement statement = CCJSqlParserUtil.parse(sql); + + List tableNameList = new TablesNamesFinder().getTableList(statement); + // sort table name + if (tableNameList != null && tableNameList.size() > 1) { + Collections.sort(tableNameList); + } + + TableSchema tableSchema = new TableSchema(); + tableSchema.setAction(getAction(statement)); + tableSchema.setTableNames(tableNameList); + return tableSchema; + } + + private static String getAction(Statement statement) { + if (statement instanceof Select) { + return "Select"; + } + return statement.getClass().getSimpleName(); + } +} + diff --git a/arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/TableSchema.java b/arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/TableSchema.java new file mode 100644 index 000000000..1824aeada --- /dev/null +++ b/arex-third-party/src/main/java/io/arex/agent/thirdparty/util/sqlparser/TableSchema.java @@ -0,0 +1,39 @@ +package io.arex.agent.thirdparty.util.sqlparser; + +import java.util.List; + +public class TableSchema { + String dbName; + /** + * Joint query sql statement parses out multiple table names + */ + List tableNames; + /** + * eg: query/insert/update/delete + */ + String action; + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public List getTableNames() { + return tableNames; + } + + public void setTableNames(List tableNames) { + this.tableNames = tableNames; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } +} From ecc8285f44afcfd325044f4cf6d94e2ea6dc05c8 Mon Sep 17 00:00:00 2001 From: Mark Zhang Date: Wed, 5 Jun 2024 15:33:23 +0800 Subject: [PATCH 11/20] feat: pass attachments to storage service for replay (#486) --- .../arex/agent/bootstrap/util/MapUtils.java | 7 ++++++ .../agent/bootstrap/util/MapUtilsTest.java | 9 ++++++++ .../inst/runtime/context/ContextManager.java | 7 ++++++ .../inst/runtime/model/ArexConstants.java | 2 +- .../runtime/context/ContextManagerTest.java | 23 +++++++++++++++---- .../dubbo/alibaba/DubboCodecExtractor.java | 2 +- .../dubbo/alibaba/DubboProviderExtractor.java | 5 ++-- .../alibaba/DubboCodecExtractorTest.java | 6 ++--- .../apache/v2/DubboProviderExtractor.java | 2 +- .../apache/v3/DubboProviderExtractor.java | 2 +- .../inst/dubbo/common/DubboExtractor.java | 5 ++++ .../inst/httpservlet/ServletAdviceHelper.java | 8 +++++-- 12 files changed, 61 insertions(+), 17 deletions(-) diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/MapUtils.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/MapUtils.java index dcba83800..7c31d9355 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/MapUtils.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/MapUtils.java @@ -79,4 +79,11 @@ public static String getString(final Map map, final K key) { } return null; } + + public static void putIfValueNotNull(Map map, String key, Object value) { + if (value == null) { + return; + } + map.put(key, value); + } } diff --git a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/MapUtilsTest.java b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/MapUtilsTest.java index df2d45846..38d7aa11e 100644 --- a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/MapUtilsTest.java +++ b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/MapUtilsTest.java @@ -84,6 +84,15 @@ void getString() { assertEquals("value", MapUtils.getString(map, "key", "default")); // key not exist return default value assertEquals("default", MapUtils.getString(map, "key1", "default")); + } + + @Test + void putIfValueNotNull() { + Map map = new HashMap<>(); + MapUtils.putIfValueNotNull(map, "key", "value"); + assertEquals("value", map.get("key")); + MapUtils.putIfValueNotNull(map, "key", null); + assertNotNull(map.get("key")); } } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ContextManager.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ContextManager.java index 55a5368ed..4f2b4dd1c 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ContextManager.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/ContextManager.java @@ -93,4 +93,11 @@ private static void publish(ArexContext context, boolean isCreate) { }); } } + + public static void setAttachment(String key, Object value) { + ArexContext context = currentContext(); + if (context != null) { + context.setAttachment(key, value); + } + } } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java index b90c9b6d4..1e75e2346 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/model/ArexConstants.java @@ -32,7 +32,7 @@ private ArexConstants() {} public static final String CURRENT_TIME_MILLIS_SIGNATURE = "java.lang.System.currentTimeMillis"; public static final String NEXT_INT_SIGNATURE = "java.util.Random.nextInt"; public static final String SERIALIZE_SKIP_INFO_CONFIG_KEY = "serializeSkipInfoList"; - public static final String SCHEDULE_REPLAY_FLAG = "arex-schedule-replay"; + public static final String SCHEDULE_REPLAY = "arex-schedule-replay"; public static final String REPLAY_ORIGINAL_MOCKER = "arex-replay-original-mocker"; public static final String AREX_EXTENSION_ATTRIBUTE = "arex-extension-attribute"; public static final String GSON_SERIALIZER = "gson"; diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/ContextManagerTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/ContextManagerTest.java index b8f67e02f..fb146a5fe 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/ContextManagerTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/ContextManagerTest.java @@ -3,10 +3,12 @@ import io.arex.agent.bootstrap.TraceContextManager; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -23,7 +25,6 @@ class ContextManagerTest { @BeforeAll static void setUp() { - Mockito.mockStatic(TraceContextManager.class); } @AfterAll @@ -34,8 +35,10 @@ static void tearDown() { @ParameterizedTest @MethodSource("currentContextCase") void currentContext(boolean createIfAbsent, String caseId, Runnable mocker, Predicate predicate) { - mocker.run(); - assertTrue(predicate.test(ContextManager.currentContext(createIfAbsent, caseId))); + try (MockedStatic traceContextManager = Mockito.mockStatic(TraceContextManager.class)) { + mocker.run(); + assertTrue(predicate.test(ContextManager.currentContext(createIfAbsent, caseId))); + } } static Stream currentContextCase() { @@ -53,7 +56,17 @@ static Stream currentContextCase() { arguments(true, null, emptyMocker, predicate1), arguments(true, null, mocker1, predicate2), arguments(true, "mock", mocker2, predicate2), - arguments(false, null, emptyMocker, predicate2) + arguments(false, null, mocker2, predicate2) ); } -} \ No newline at end of file + + @Test + void setAttachment() { + try (MockedStatic traceContextManager = Mockito.mockStatic(TraceContextManager.class)) { + Mockito.when(TraceContextManager.get(any(Boolean.class))).thenReturn("record-id"); + ContextManager.currentContext(true, null); + ContextManager.setAttachment("attachment-key", "attachment-value"); + assertEquals("attachment-value", ContextManager.currentContext().getAttachment("attachment-key")); + } + } +} diff --git a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractor.java b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractor.java index 4b50a2e1c..a94355ec9 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractor.java +++ b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractor.java @@ -65,7 +65,7 @@ public static boolean writeAttachments(Channel channel, ObjectOutput out, Object } private static boolean isNeedAttach(String version, Map attachments) { - if (!Boolean.TRUE.toString().equals(attachments.get(ArexConstants.SCHEDULE_REPLAY_FLAG))) { + if (StringUtil.isEmpty(attachments.get(ArexConstants.SCHEDULE_REPLAY))) { return false; } if (StringUtil.isEmpty(version)) { diff --git a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractor.java b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractor.java index cfd0d715d..53e6e948e 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractor.java +++ b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/main/java/io/arex/inst/dubbo/alibaba/DubboProviderExtractor.java @@ -29,7 +29,7 @@ public static void onServiceEnter(Invoker invoker, Invocation invocation) { String excludeMockTemplate = adapter.getExcludeMockTemplate(); RequestHandlerManager.preHandle(invocation.getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); CaseEventDispatcher.onEvent(CaseEvent.ofCreateEvent(EventSource.of(caseId, excludeMockTemplate))); - ContextManager.currentContext().setAttachment(ArexConstants.FORCE_RECORD, adapter.forceRecord()); + addAttachmentsToContext(adapter); RequestHandlerManager.handleAfterCreateContext(invocation.getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); invocation.getAttachments().put(ArexConstants.ORIGINAL_REQUEST, Serializer.serialize(invocation.getArguments())); } @@ -67,8 +67,7 @@ private static void setAttachment(Invocation invocation, String key, String valu * the set result is used in DubboCodec#encodeResponseData (serialize) */ result.getAttachments().put(key, value); - result.getAttachments().put(ArexConstants.SCHEDULE_REPLAY_FLAG, - invocation.getAttachment(ArexConstants.SCHEDULE_REPLAY_FLAG, Boolean.FALSE.toString())); + result.getAttachments().put(ArexConstants.SCHEDULE_REPLAY, invocation.getAttachment(ArexConstants.SCHEDULE_REPLAY)); } } } diff --git a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractorTest.java b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractorTest.java index 6c3dd05a4..f9edfed68 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractorTest.java +++ b/arex-instrumentation/dubbo/arex-dubbo-alibaba/src/test/java/io/arex/inst/dubbo/alibaba/DubboCodecExtractorTest.java @@ -54,10 +54,10 @@ static Stream writeAttachmentsCase() { Runnable emptyMocker = () -> { }; Runnable mocker1 = () -> { - rpcResult.setAttachment(ArexConstants.REPLAY_ID, "mock"); + rpcResult.setAttachment(ArexConstants.REPLAY_ID, ""); }; Runnable mocker2 = () -> { - rpcResult.setAttachment(ArexConstants.SCHEDULE_REPLAY_FLAG, Boolean.TRUE.toString()); + rpcResult.setAttachment(ArexConstants.SCHEDULE_REPLAY, Boolean.TRUE.toString()); }; Runnable mocker3 = () -> { Mockito.when(url.getParameter("version", "")).thenReturn("2.0"); @@ -96,4 +96,4 @@ static Stream writeAttachmentsCase() { arguments(rpcResult, mocker9, assertFalse) ); } -} \ No newline at end of file +} diff --git a/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/main/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractor.java b/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/main/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractor.java index 4c8119614..aa0fe5c2a 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/main/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractor.java +++ b/arex-instrumentation/dubbo/arex-dubbo-apache-v2/src/main/java/io/arex/inst/dubbo/apache/v2/DubboProviderExtractor.java @@ -32,7 +32,7 @@ public static void onServiceEnter(Invoker invoker, Invocation invocation) { String excludeMockTemplate = adapter.getExcludeMockTemplate(); RequestHandlerManager.preHandle(invocation.getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); CaseEventDispatcher.onEvent(CaseEvent.ofCreateEvent(EventSource.of(caseId, excludeMockTemplate))); - ContextManager.currentContext().setAttachment(ArexConstants.FORCE_RECORD, adapter.forceRecord()); + addAttachmentsToContext(adapter); RequestHandlerManager.handleAfterCreateContext(invocation.getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); invocation.setAttachment(ArexConstants.ORIGINAL_REQUEST, Serializer.serialize(invocation.getArguments())); } diff --git a/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java b/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java index e7a43b734..27eb1b21f 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java +++ b/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java @@ -29,7 +29,7 @@ public static void onServiceEnter(Invoker invoker, Invocation invocation) { String excludeMockTemplate = adapter.getExcludeMockTemplate(); RequestHandlerManager.preHandle(invocation.getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); CaseEventDispatcher.onEvent(CaseEvent.ofCreateEvent(EventSource.of(caseId, excludeMockTemplate))); - ContextManager.currentContext().setAttachment(ArexConstants.FORCE_RECORD, adapter.forceRecord()); + addAttachmentsToContext(adapter); RequestHandlerManager.handleAfterCreateContext(invocation.getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); invocation.getAttributes().put(ArexConstants.ORIGINAL_REQUEST, Serializer.serialize(invocation.getArguments())); } diff --git a/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java b/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java index 48945c040..8829f04af 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java +++ b/arex-instrumentation/dubbo/arex-dubbo-common/src/main/java/io/arex/inst/dubbo/common/DubboExtractor.java @@ -57,4 +57,9 @@ protected static void setResponseHeader(BiConsumer consumer) { consumer.accept(ArexConstants.REPLAY_ID, ContextManager.currentContext().getReplayId()); } } + + protected static void addAttachmentsToContext(AbstractAdapter adapter) { + ContextManager.setAttachment(ArexConstants.FORCE_RECORD, adapter.forceRecord()); + ContextManager.setAttachment(ArexConstants.SCHEDULE_REPLAY, adapter.getAttachment(ArexConstants.SCHEDULE_REPLAY)); + } } diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java index 6db587ba0..e4388cdef 100644 --- a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/ServletAdviceHelper.java @@ -119,8 +119,7 @@ public static Pair onServiceEnter( String caseId = adapter.getRequestHeader(httpServletRequest, ArexConstants.RECORD_ID); String excludeMockTemplate = adapter.getRequestHeader(httpServletRequest, ArexConstants.HEADER_EXCLUDE_MOCK); CaseEventDispatcher.onEvent(CaseEvent.ofCreateEvent(EventSource.of(caseId, excludeMockTemplate))); - ContextManager.currentContext().setAttachment(ArexConstants.FORCE_RECORD, - adapter.getRequestHeader(httpServletRequest, ArexConstants.FORCE_RECORD, ArexConstants.HEADER_X_PREFIX)); + addAttachmentsToContext(adapter, httpServletRequest); RequestHandlerManager.handleAfterCreateContext(httpServletRequest, adapter.getServletVersion()); } @@ -285,4 +284,9 @@ private static String getRedirectRecordId(ServletAdapter void addAttachmentsToContext(ServletAdapter adapter, TRequest request) { + ContextManager.setAttachment(ArexConstants.FORCE_RECORD, adapter.getRequestHeader(request, ArexConstants.FORCE_RECORD, ArexConstants.HEADER_X_PREFIX)); + ContextManager.setAttachment(ArexConstants.SCHEDULE_REPLAY, adapter.getRequestHeader(request, ArexConstants.SCHEDULE_REPLAY)); + } } From 0680c89eb3182f54b083a28e341c6d2bcc656d83 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 6 Jun 2024 16:50:17 +0800 Subject: [PATCH 12/20] feat: optimize sql parse (#499) * feat: optimize sql parse * feat: optimize sql parse comment * feat: optimize sql parse UT --- .../arex/inst/runtime/util/DatabaseUtils.java | 42 +- .../inst/runtime/util/DatabaseUtilsTest.java | 78 +- .../src/test/resources/test_sql.txt | 2928 +++++++++++++++++ .../database/common/DatabaseExtractor.java | 2 +- 4 files changed, 3033 insertions(+), 17 deletions(-) create mode 100644 arex-instrumentation-api/src/test/resources/test_sql.txt diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java index 220a2fc7f..fd86be056 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/DatabaseUtils.java @@ -1,42 +1,70 @@ package io.arex.inst.runtime.util; +import io.arex.agent.bootstrap.util.CollectionUtil; import io.arex.agent.bootstrap.util.StringUtil; import io.arex.agent.thirdparty.util.sqlparser.JSqlParserUtil; import io.arex.agent.thirdparty.util.sqlparser.TableSchema; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.log.LogManager; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class DatabaseUtils { private static final String DELIMITER = "@"; + private static final int THRESHOLD = 50000; + /** * eg: db1@table1,table2@select@operation1;db2@table3,table4@select@operation2; */ public static String regenerateOperationName(String dbName, String operationName, String sqlText) { - // If the operation name already contains @, it means that it has already been generated and does not need to be generated again - if (StringUtil.isEmpty(sqlText) || StringUtil.isEmpty(operationName) || operationName.contains(DELIMITER)) { + if (StringUtil.isEmpty(sqlText) || !needRegenerate(dbName)) { return operationName; } String[] sqls = StringUtil.split(sqlText, ';'); List operationNames = new ArrayList<>(sqls.length); for (String sql : sqls) { - if (StringUtil.isEmpty(sql)) { + if (StringUtil.isEmpty(sql) || sql.length() > THRESHOLD) { + // if exceed the threshold, too large may be due parse stack overflow continue; } try{ TableSchema tableSchema = JSqlParserUtil.parse(sql); tableSchema.setDbName(dbName); operationNames.add(regenerateOperationName(tableSchema, operationName)); - } catch (Exception e) { - // ignore + } catch (Throwable e) { + // may be thrown error + LogManager.warn("parse sql error", StringUtil.format("sql: %s", sql), e); } } + if (CollectionUtil.isEmpty(operationNames)) { + return operationName; + } return StringUtil.join(operationNames, ";"); } + /** + * compatible with the old version, if the excludeMockTemplate config not contains '@', it means that not need generate + */ + private static boolean needRegenerate(String dbName) { + Map> excludeMockTemplate = ContextManager.currentContext().getExcludeMockTemplate(); + if (excludeMockTemplate == null) { + return false; + } + Set operationSet = excludeMockTemplate.get(dbName); + if (CollectionUtil.isEmpty(operationSet)) { + return false; + } + for (String operation : operationSet) { + if (operation != null && operation.contains(DELIMITER)) { + return true; + } + } + return false; + } + private static String regenerateOperationName(TableSchema tableSchema, String originOperationName) { return new StringBuilder(100).append(StringUtil.defaultString(tableSchema.getDbName())).append(DELIMITER) .append(StringUtil.defaultString(StringUtil.join(tableSchema.getTableNames(), ","))).append(DELIMITER) diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java index ae8db1521..704d397f0 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/util/DatabaseUtilsTest.java @@ -1,20 +1,80 @@ package io.arex.inst.runtime.util; +import io.arex.inst.runtime.context.ArexContext; +import io.arex.inst.runtime.context.ContextManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.params.provider.Arguments.arguments; class DatabaseUtilsTest { + static String largeSql; + + @BeforeAll + static void setUp() throws IOException { + Mockito.mockStatic(ContextManager.class); + largeSql = new String(Files.readAllBytes(Paths.get("src/test/resources/test_sql.txt"))); + } + + @AfterAll + static void tearDown() { + largeSql = null; + Mockito.clearAllCaches(); + } + @ParameterizedTest - @CsvSource(value ={ - "database1, @, select * from table1, @", - "database1, query, ;, ''", - "database1, query, mock, ''", - "database1, query, select * from table1, database1@table1@Select@query" - }, nullValues={"null"}) - void regenerateOperationName(String dbName, String operationName, String sqlText, String expect) { - assertEquals(expect, DatabaseUtils.regenerateOperationName(dbName, operationName, sqlText)); + @MethodSource("regenerateOperationNameCase") + void regenerateOperationName(String dbName, String operationName, String sqlText, Runnable mocker, Predicate predicate) { + mocker.run(); + String result = DatabaseUtils.regenerateOperationName(dbName, operationName, sqlText); + assertTrue(predicate.test(result)); + } + + static Stream regenerateOperationNameCase() { + Map> excludeMockTemplate = new HashMap<>(); + Set operationSet = new HashSet<>(); + ArexContext context = ArexContext.of("mock"); + Runnable noExcludeMockTemplateMocker = () -> { + Mockito.when(ContextManager.currentContext()).thenReturn(context); + }; + Runnable noOperationSetMocker = () -> { + context.setExcludeMockTemplate(excludeMockTemplate); + }; + Runnable hasOperationSetMocker = () -> { + operationSet.add("operation1"); + excludeMockTemplate.put("database1", operationSet); + }; + Runnable needRegenerateMocker = () -> { + operationSet.clear(); + operationSet.add("database1@table1@Select@query"); + excludeMockTemplate.put("database1", operationSet); + }; + + Predicate predicate1 = "@"::equals; + Predicate predicate2 = "database1@table1@Select@query"::equals; + + String normalSql = "select * from table1"; + + return Stream.of( + arguments("database1", "@", normalSql, noExcludeMockTemplateMocker, predicate1), + arguments("database1", "@", normalSql, noOperationSetMocker, predicate1), + arguments("database1", "@", normalSql, hasOperationSetMocker, predicate1), + arguments("database1", "@", largeSql, needRegenerateMocker, predicate1), + arguments("database1", "query", normalSql, needRegenerateMocker, predicate2), + arguments("database1", "@", "wrong sql", needRegenerateMocker, predicate1) + ); } } \ No newline at end of file diff --git a/arex-instrumentation-api/src/test/resources/test_sql.txt b/arex-instrumentation-api/src/test/resources/test_sql.txt new file mode 100644 index 000000000..12bfde2df --- /dev/null +++ b/arex-instrumentation-api/src/test/resources/test_sql.txt @@ -0,0 +1,2928 @@ +SELECT s.ci_no, s.* FROM server AS s WHERE s.exclusive IN ( ? , ? , ? ) + AND + ( s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + OR + s.hostname LIKE concat('%', ?, '%') + ) + + ORDER BY hostname ASC \ No newline at end of file diff --git a/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java b/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java index 1c546b670..5d66776a5 100644 --- a/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java +++ b/arex-instrumentation/database/arex-database-common/src/main/java/io/arex/inst/database/common/DatabaseExtractor.java @@ -94,7 +94,7 @@ public MockResult replay() { public MockResult replay(String serializer) { String serviceKey = StringUtil.defaultIfEmpty(this.dbName, ArexConstants.DATABASE); - String operationKey = DatabaseUtils.regenerateOperationName(dbName, this.methodName, this.sql); + String operationKey = DatabaseUtils.regenerateOperationName(serviceKey, this.methodName, this.sql); boolean ignoreMockResult = IgnoreUtils.ignoreMockResult(serviceKey, operationKey); Mocker replayMocker = MockUtils.replayMocker(makeMocker(null, serializer)); From 7a8096f6a38f3f30f5b0c74f962d1261fbafffc9 Mon Sep 17 00:00:00 2001 From: DanLi39 <147678474+DanLi39@users.noreply.github.com> Date: Fri, 7 Jun 2024 10:21:19 +0800 Subject: [PATCH 13/20] fix: ignore type matching for AREX-related content (#498) --- .../io/arex/inst/extension/matcher/IgnoredTypesMatcher.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java index 12ed47be0..9c9c07445 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java @@ -1,5 +1,6 @@ package io.arex.inst.extension.matcher; +import io.arex.agent.bootstrap.cache.AdviceInjectorCache; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -29,6 +30,7 @@ public boolean matches(TypeDescription target) { } } - return false; + // check if the target is in the AdviceInjectorCache + return AdviceInjectorCache.contains(target.getName()); } } From 8fa5c0457b41a729b50936365dc097ac4272c3a0 Mon Sep 17 00:00:00 2001 From: Mark Zhang Date: Tue, 11 Jun 2024 18:16:56 +0800 Subject: [PATCH 14/20] fix: mock tags being replaced on replay (#503) --- .../bootstrap/constants/ConfigConstants.java | 1 + .../agent/bootstrap/model/ArexMocker.java | 22 ++++++++++++++++--- .../io/arex/inst/runtime/util/MockUtils.java | 4 +--- .../foundation/services/ConfigService.java | 10 +++++---- .../feign/FeignClientInstrumentationTest.java | 3 +-- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java index ba71c6b34..e27bdd413 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java @@ -35,4 +35,5 @@ private ConfigConstants() { public static final String COVERAGE_PACKAGES = "arex.coverage.packages"; public static final String APP_CLASSLOADER_NAME = "jdk.internal.loader.ClassLoaders$AppClassLoader"; public static final String API_TOKEN = "arex.api.token"; + public static final String MOCKER_TAGS = "arex.mocker.tags"; } diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/ArexMocker.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/ArexMocker.java index 8fc902750..d6d5a2b9d 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/ArexMocker.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/ArexMocker.java @@ -1,10 +1,10 @@ package io.arex.agent.bootstrap.model; -import java.util.HashMap; +import io.arex.agent.bootstrap.constants.ConfigConstants; +import java.util.Collections; import java.util.Map; public class ArexMocker implements Mocker { - public static final Map TAGS = new HashMap<>(); private String id; private MockCategoryType categoryType; private String replayId; @@ -17,16 +17,31 @@ public class ArexMocker implements Mocker { private Mocker.Target targetResponse; private boolean needMerge; private String operationName; + private Map tags; + /** + * The default constructor is for deserialization + */ public ArexMocker() { } public ArexMocker(MockCategoryType categoryType) { this.categoryType = categoryType; + this.appId = System.getProperty(ConfigConstants.SERVICE_NAME); + this.recordVersion = System.getProperty(ConfigConstants.AGENT_VERSION); + this.tags = (Map) System.getProperties().getOrDefault(ConfigConstants.MOCKER_TAGS, Collections.emptyMap()); } + /** + * Put tag into the tags map will throw {@link UnsupportedOperationException}. + * @return the tags map + */ public Map getTags() { - return TAGS; + return this.tags; + } + + public void setTags(Map tags) { + this.tags = tags; } public String getId() { @@ -58,6 +73,7 @@ public String getRecordVersion() { return this.recordVersion; } + @Deprecated public void setRecordVersion(String recordVersion) { this.recordVersion = recordVersion; } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/MockUtils.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/MockUtils.java index fdc0f4c94..4bf80aefc 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/MockUtils.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/util/MockUtils.java @@ -73,7 +73,7 @@ public static ArexMocker createNettyProvider(String pattern) { } public static ArexMocker create(MockCategoryType categoryType, String operationName) { - ArexMocker mocker = new ArexMocker(); + ArexMocker mocker = new ArexMocker(categoryType); long createTime = System.currentTimeMillis(); ArexContext context = ContextManager.currentContext(); if (context != null) { @@ -82,12 +82,10 @@ public static ArexMocker create(MockCategoryType categoryType, String operationN createTime += context.calculateSequence(); } mocker.setCreationTime(createTime); - mocker.setAppId(System.getProperty("arex.service.name")); mocker.setCategoryType(categoryType); mocker.setOperationName(operationName); mocker.setTargetRequest(new Target()); mocker.setTargetResponse(new Target()); - mocker.setRecordVersion(Config.get().getRecordVersion()); return mocker; } diff --git a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/services/ConfigService.java b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/services/ConfigService.java index 04bbb2a42..0a57bb7c0 100644 --- a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/services/ConfigService.java +++ b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/services/ConfigService.java @@ -2,7 +2,6 @@ import com.google.gson.Gson; import io.arex.agent.bootstrap.constants.ConfigConstants; -import io.arex.agent.bootstrap.model.ArexMocker; import io.arex.agent.bootstrap.util.MapUtils; import io.arex.foundation.model.*; import io.arex.foundation.config.ConfigManager; @@ -10,6 +9,7 @@ import io.arex.foundation.util.NetUtils; import io.arex.agent.bootstrap.util.StringUtil; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -130,22 +130,24 @@ AgentStatusEnum getAgentStatus() { public Map getSystemProperties() { Properties properties = System.getProperties(); Map map = MapUtils.newHashMapWithExpectedSize(properties.size()); + Map mockerTags = new HashMap<>(); for (Map.Entry entry : properties.entrySet()) { String key = String.valueOf(entry.getKey()); String value = String.valueOf(entry.getValue()); map.put(key, value); - buildTags(key, value); + buildTags(mockerTags, key, value); } + properties.put(ConfigConstants.MOCKER_TAGS, Collections.unmodifiableMap(mockerTags)); return map; } /** * ex: -Darex.tags.xxx=xxx */ - private void buildTags(String key, String value) { + private void buildTags(Map mockerTags, String key, String value) { if (StringUtil.startWith(key, TAGS_PREFIX)) { TAGS_PROPERTIES.put(key, value); - ArexMocker.TAGS.put(key.substring(TAGS_PREFIX.length()), value); + mockerTags.put(key.substring(TAGS_PREFIX.length()), value); } } diff --git a/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java b/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java index 503aa97a6..275adba80 100644 --- a/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java +++ b/arex-instrumentation/httpclient/arex-httpclient-feign/src/test/java/io/arex/inst/httpclient/feign/FeignClientInstrumentationTest.java @@ -5,7 +5,6 @@ import feign.Request; import feign.Response; import io.arex.agent.bootstrap.model.MockResult; -import io.arex.inst.httpclient.common.HttpClientAdapter; import io.arex.inst.httpclient.common.HttpClientExtractor; import io.arex.inst.runtime.context.ContextManager; import io.arex.inst.runtime.context.RepeatedCollectManager; @@ -65,7 +64,7 @@ void onEnter() { // need replay and not exclude operation Mockito.when(ContextManager.needReplay()).thenReturn(true); - assertThrows(NullPointerException.class, () -> FeignClientInstrumentation.ExecuteAdvice.onEnter(request, null, null, null)); + assertTrue(FeignClientInstrumentation.ExecuteAdvice.onEnter(request, null, null, null)); } @Test From ef1853dde838c43a58a9f2d958f98069f66b7e8a Mon Sep 17 00:00:00 2001 From: pangdayuan1 <116159079+pangdayuan1@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:59:22 +0800 Subject: [PATCH 15/20] feat: add arex-service-name and arex-agent-version headers (#506) --- .../foundation/util/httpclient/AsyncHttpClientUtil.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/util/httpclient/AsyncHttpClientUtil.java b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/util/httpclient/AsyncHttpClientUtil.java index a8dcbeea3..a4a6b9691 100644 --- a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/util/httpclient/AsyncHttpClientUtil.java +++ b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/util/httpclient/AsyncHttpClientUtil.java @@ -113,6 +113,8 @@ private static HttpPost prepareHttpRequest(String uri, int connectTimeout, int s httpPost.addHeader(ClientConfig.HEADER_ACCEPT); httpPost.addHeader(ClientConfig.HEADER_USER_AGENT); httpPost.addHeader(ClientConfig.HEADER_API_TOKEN); + httpPost.addHeader(ClientConfig.HEADER_SERVICE_NAME); + httpPost.addHeader(ClientConfig.HEADER_AGENT_VERSION); return httpPost; } @@ -151,5 +153,9 @@ static class ClientConfig { String.format("arex-async-http-client-%s", ConfigManager.INSTANCE.getAgentVersion())); private static final Header HEADER_API_TOKEN = new BasicHeader("arex-api-token", System.getProperty(ConfigConstants.API_TOKEN, StringUtil.EMPTY)); + private static final Header HEADER_SERVICE_NAME = new BasicHeader("arex-service-name", + System.getProperty(ConfigConstants.SERVICE_NAME, StringUtil.EMPTY)); + private static final Header HEADER_AGENT_VERSION = new BasicHeader("arex-agent-version", + System.getProperty(ConfigConstants.AGENT_VERSION, StringUtil.EMPTY)); } } From 9f310d1ef8b4517d8349771a541c58eefb0d02a3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 25 Jun 2024 16:43:01 +0800 Subject: [PATCH 16/20] fix: latency clear and compatible redisson version (#511) * fix: latency clear and compatible redisson version * fix: sonar * fix: remove latencyMap * fix: remove cleanup threshold * fix: UT * feat: add comment --- .../context/LatencyContextHashMap.java | 46 +++---------------- .../context/LatencyContextHashMapTest.java | 13 ++---- .../v3/wrapper/RedissonBucketWrapper.java | 4 +- 3 files changed, 13 insertions(+), 50 deletions(-) diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/LatencyContextHashMap.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/LatencyContextHashMap.java index 4a6de3e53..811f19331 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/LatencyContextHashMap.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/context/LatencyContextHashMap.java @@ -8,21 +8,20 @@ import java.util.concurrent.locks.ReentrantLock; /** - * Only used for ContextManager + * Only used for ContextManager
+ * delayed clean context in asynchronous situations, + * the purpose is to ensure that the context can also be obtained during recording in async threads */ final class LatencyContextHashMap extends ConcurrentHashMap { - private static final int CLEANUP_THRESHOLD = 10; private static final long RECORD_TTL_MILLIS = TimeUnit.MINUTES.toMillis(1); private static final ReentrantLock CLEANUP_LOCK = new ReentrantLock(); - private ConcurrentHashMap latencyMap; @Override public ArexContext get(Object key) { if (key == null) { return null; } - ArexContext context = super.get(key); - return context == null ? initOrGet(key) : context; + return super.get(key); } @Override @@ -30,51 +29,20 @@ public ArexContext remove(Object key) { if (key == null) { return null; } - ArexContext context = super.get(key); - if (latencyMap != null && context != null) { - latencyMap.put(String.valueOf(key), context); - } - // todo: time put into ArexContext - if (latencyMap == null) { - TimeCache.remove(String.valueOf(key)); - } - super.remove(key); overdueCleanUp(); - return context; - } - - private ArexContext initOrGet(Object key) { - if (latencyMap == null) { - latencyMap = new ConcurrentHashMap<>(); - return null; - } - return latencyMap.get(key); + return super.get(key); } private void overdueCleanUp() { - if (latencyMap != null && CLEANUP_LOCK.tryLock()) { + if (CLEANUP_LOCK.tryLock()) { try { long now = System.currentTimeMillis(); - for (Map.Entry entry: latencyMap.entrySet()) { + for (Map.Entry entry: super.entrySet()) { if (isExpired(entry.getValue().getCreateTime(), now)) { // clear context attachments entry.getValue().clear(); - latencyMap.remove(entry.getKey()); TimeCache.remove(entry.getKey()); - } - } - } finally { - CLEANUP_LOCK.unlock(); - } - } - - // Compatible where map.remove() not called - if (this.mappingCount() > CLEANUP_THRESHOLD && CLEANUP_LOCK.tryLock()) { - try { - long now = System.currentTimeMillis(); - for (Map.Entry entry: super.entrySet()) { - if (isExpired(entry.getValue().getCreateTime(), now)) { super.remove(entry.getKey()); } } diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/LatencyContextHashMapTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/LatencyContextHashMapTest.java index 9f43cc02d..2ef8e1ecd 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/LatencyContextHashMapTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/context/LatencyContextHashMapTest.java @@ -1,11 +1,10 @@ package io.arex.inst.runtime.context; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - import java.util.Map; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + class LatencyContextHashMapTest { private static final Map RECORD_MAP = new LatencyContextHashMap(); @@ -23,11 +22,7 @@ void test() { // remove key context = RECORD_MAP.remove(key); assertEquals(key, context.getCaseId()); - assertEquals(0, RECORD_MAP.size()); - - // get key again, init latencyMap - context = RECORD_MAP.get(key); - assertNull(context); + assertEquals(1, RECORD_MAP.size()); // create key2 String key2 = "arex-test-id-2"; @@ -37,7 +32,7 @@ void test() { // remove key2 context = RECORD_MAP.remove(key2); assertEquals(key2, context.getCaseId()); - assertEquals(0, RECORD_MAP.size()); + assertEquals(2, RECORD_MAP.size()); context = RECORD_MAP.get(key2); assertEquals(key2, context.getCaseId()); diff --git a/arex-instrumentation/redis/arex-redission-v3/src/main/java/io/arex/inst/redisson/v3/wrapper/RedissonBucketWrapper.java b/arex-instrumentation/redis/arex-redission-v3/src/main/java/io/arex/inst/redisson/v3/wrapper/RedissonBucketWrapper.java index bcd8e164b..3fa4f352b 100644 --- a/arex-instrumentation/redis/arex-redission-v3/src/main/java/io/arex/inst/redisson/v3/wrapper/RedissonBucketWrapper.java +++ b/arex-instrumentation/redis/arex-redission-v3/src/main/java/io/arex/inst/redisson/v3/wrapper/RedissonBucketWrapper.java @@ -220,13 +220,13 @@ public RFuture clearExpireAsync() { @Override public RFuture remainTimeToLiveAsync() { - return RedissonWrapperCommon.delegateCall(redisUri, RedisCommands.PTTL.getName(), getRawName(), + return RedissonWrapperCommon.delegateCall(redisUri, RedisCommands.PTTL.getName(), this.name, () -> super.remainTimeToLiveAsync()); } @Override public RFuture getExpireTimeAsync() { - return RedissonWrapperCommon.delegateCall(redisUri, RedisCommands.PEXPIRETIME.getName(), getRawName(), + return RedissonWrapperCommon.delegateCall(redisUri, RedisCommands.PEXPIRETIME.getName(), this.name, () -> super.getExpireTimeAsync()); } From fd8501fcb6a83f959241cf1a63e085833bfe3ded Mon Sep 17 00:00:00 2001 From: LHHDZ Date: Thu, 27 Jun 2024 15:28:59 +0800 Subject: [PATCH 17/20] feat: support ignore transform by configured type or classloader prefixes (#514) - Ignore types by setting system properties by -Darex.ignore.type.prefixes=xxx.type, separate multiple values with commas. - Ignore classloader by setting system properties by -Darex.ignore.classloader.prefixes=xxx.classloader, separate multiple values with commas. --------- Co-authored-by: mr3 --- .../bootstrap/constants/ConfigConstants.java | 8 ++++ .../InstrumentationInstaller.java | 10 ++-- .../matcher/IgnoreClassloaderMatcher.java | 22 +++++++-- .../extension/matcher/IgnoredRawMatcher.java | 29 +++++++++++ .../matcher/IgnoredTypesMatcher.java | 15 ++++-- .../matcher/IgnoreClassloaderMatcherTest.java | 25 ++++++++-- .../matcher/IgnoredRawMatcherTest.java | 48 +++++++++++++++++++ .../matcher/IgnoredTypesMatcherTest.java | 4 +- .../arex/foundation/config/ConfigManager.java | 28 +++++++++++ 9 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredRawMatcher.java create mode 100644 arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredRawMatcherTest.java diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java index e27bdd413..3db3e46db 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/constants/ConfigConstants.java @@ -16,6 +16,14 @@ private ConfigConstants() { public static final String ALLOW_TIME_FROM = "arex.allow.time.from"; public static final String ALLOW_TIME_TO = "arex.allow.time.to"; + /** + * Assign values via -Darex.ignore.type.prefixes=xxx.type, separate multiple values with commas. + */ + public static final String IGNORED_TYPE_PREFIXES = "arex.ignore.type.prefixes"; + /** + * Assign values via -Darex.ignore.classloader.prefixes=xxx.classloader, separate multiple values with commas. + */ + public static final String IGNORED_CLASS_LOADER_PREFIXES = "arex.ignore.classloader.prefixes"; public static final String DISABLE_MODULE = "arex.disable.instrumentation.module"; public static final String RETRANSFORM_MODULE = "arex.retransform.instrumentation.module"; diff --git a/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java b/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java index f829a3d2c..312223c07 100644 --- a/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java +++ b/arex-agent-core/src/main/java/io/arex/agent/instrumentation/InstrumentationInstaller.java @@ -8,7 +8,7 @@ import io.arex.foundation.config.ConfigManager; import io.arex.agent.bootstrap.util.CollectionUtil; -import io.arex.inst.extension.matcher.IgnoredTypesMatcher; +import io.arex.inst.extension.matcher.IgnoredRawMatcher; import io.arex.inst.runtime.model.DynamicClassEntity; import io.arex.inst.runtime.model.DynamicClassStatusEnum; import io.arex.agent.bootstrap.util.ServiceLoader; @@ -181,11 +181,13 @@ private AgentBuilder.Identified.Extendable installMethod(AgentBuilder.Identified private AgentBuilder getAgentBuilder() { // config may use to add some classes to be ignored in future long buildBegin = System.currentTimeMillis(); - AgentBuilder builder = new AgentBuilder.Default( + + return new AgentBuilder.Default( new ByteBuddy().with(MethodGraph.Compiler.ForDeclaredMethods.INSTANCE)) .enableNativeMethodPrefix("arex_") .disableClassFormatChanges() - .ignore(new IgnoredTypesMatcher()) + .ignore(new IgnoredRawMatcher(ConfigManager.INSTANCE.getIgnoreTypePrefixes(), + ConfigManager.INSTANCE.getIgnoreClassLoaderPrefixes())) .with(new TransformListener()) .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE) @@ -194,8 +196,6 @@ private AgentBuilder getAgentBuilder() { .with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST) .with(AgentBuilder.LocationStrategy.ForClassLoader.STRONG .withFallbackTo(ClassFileLocator.ForClassLoader.ofSystemLoader())); - - return builder; } private boolean disabledModule(String moduleName) { diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java index 2364e1356..3ea0f69d5 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcher.java @@ -1,12 +1,21 @@ package io.arex.inst.extension.matcher; +import io.arex.agent.bootstrap.util.CollectionUtil; import io.arex.agent.bootstrap.util.StringUtil; +import java.util.List; import net.bytebuddy.matcher.ElementMatcher; public class IgnoreClassloaderMatcher extends ElementMatcher.Junction.AbstractBase { static final String BYTE_BUDDY_PREFIX = StringUtil.removeShadePrefix("net.bytebuddy."); - private final ElementMatcher matcher; + + private static final List IGNORED_CLASSLOADER_PREFIXES = CollectionUtil.newArrayList( + "sun.reflect.", "jdk.internal.reflect.", IgnoreClassloaderMatcher.BYTE_BUDDY_PREFIX); + private ElementMatcher matcher; + + public IgnoreClassloaderMatcher(List ignoreClassLoaderPrefixes) { + IGNORED_CLASSLOADER_PREFIXES.addAll(ignoreClassLoaderPrefixes); + } public IgnoreClassloaderMatcher(ElementMatcher matcher) { this.matcher = matcher; @@ -19,9 +28,14 @@ public boolean matches(ClassLoader loader) { } String loaderName = loader.getClass().getName(); - if (loaderName.startsWith("sun.reflect") || - loaderName.startsWith("jdk.internal.reflect") || - loaderName.startsWith(BYTE_BUDDY_PREFIX)) { + + for (String ignored : IGNORED_CLASSLOADER_PREFIXES) { + if (loaderName.startsWith(ignored)) { + return true; + } + } + + if (matcher == null) { return false; } diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredRawMatcher.java b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredRawMatcher.java new file mode 100644 index 000000000..c8cdcfb67 --- /dev/null +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredRawMatcher.java @@ -0,0 +1,29 @@ +package io.arex.inst.extension.matcher; + +import net.bytebuddy.agent.builder.AgentBuilder; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.utility.JavaModule; +import net.bytebuddy.utility.nullability.MaybeNull; + +import java.security.ProtectionDomain; +import java.util.List; + +public class IgnoredRawMatcher implements AgentBuilder.RawMatcher { + private final ElementMatcher.Junction typeMatcher; + private final ElementMatcher.Junction classLoaderMatcher; + + public IgnoredRawMatcher(List ignoreTypePrefixes, List ignoreClassLoaderPrefixes) { + this.typeMatcher = new IgnoredTypesMatcher(ignoreTypePrefixes); + this.classLoaderMatcher = new IgnoreClassloaderMatcher(ignoreClassLoaderPrefixes); + } + + @Override + public boolean matches(TypeDescription typeDescription, + @MaybeNull ClassLoader classLoader, + @MaybeNull JavaModule module, + @MaybeNull Class classBeingRedefined, + ProtectionDomain protectionDomain) { + return typeMatcher.matches(typeDescription) || classLoaderMatcher.matches(classLoader); + } +} diff --git a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java index 9c9c07445..960ba1dac 100644 --- a/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/extension/matcher/IgnoredTypesMatcher.java @@ -1,15 +1,22 @@ package io.arex.inst.extension.matcher; import io.arex.agent.bootstrap.cache.AdviceInjectorCache; +import io.arex.agent.bootstrap.util.CollectionUtil; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; +import java.util.List; + public class IgnoredTypesMatcher extends ElementMatcher.Junction.AbstractBase { - private static final String[] IGNORED_STARTS_WITH_NAME = new String[]{ + private static final List IGNORED_TYPE_PREFIXES = CollectionUtil.newArrayList( "io.arex.", "shaded.", IgnoreClassloaderMatcher.BYTE_BUDDY_PREFIX, - "sun.reflect.", "org.springframework.boot.autoconfigure", "com.intellij."}; + "sun.reflect.", "org.springframework.boot.autoconfigure", "com.intellij."); + + private static final String[] IGNORED_CONTAINS_NAME = new String[]{"javassist.", ".asm.", ".reflectasm.", IgnoreClassloaderMatcher.BYTE_BUDDY_PREFIX}; - private static final String[] IGNORED_CONTAINS_NAME = new String[]{"javassist.", ".asm.", ".reflectasm."}; + public IgnoredTypesMatcher(List ignoreTypePrefixes) { + IGNORED_TYPE_PREFIXES.addAll(ignoreTypePrefixes); + } @Override public boolean matches(TypeDescription target) { @@ -18,7 +25,7 @@ public boolean matches(TypeDescription target) { } String name = target.getActualName(); - for (String ignored : IGNORED_STARTS_WITH_NAME) { + for (String ignored : IGNORED_TYPE_PREFIXES) { if (name.startsWith(ignored)) { return true; } diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java index a30dcdf58..b8d1ab8c7 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoreClassloaderMatcherTest.java @@ -2,13 +2,15 @@ import static org.junit.jupiter.api.Assertions.*; -import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.util.Collections; + import org.junit.jupiter.api.Test; class IgnoreClassloaderMatcherTest { @Test - void matches() { + void testMatchesMatcher() { IgnoreClassloaderMatcher matcher = new IgnoreClassloaderMatcher( new HasClassNameMatcher("io.arex.inst.extension.matcher.HasClassNameMatcher")); @@ -16,4 +18,21 @@ void matches() { assertTrue(matcher.matches(Thread.currentThread().getContextClassLoader())); } -} \ No newline at end of file + + @Test + void testMatchesIgnoredLoaders() { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + IgnoreClassloaderMatcher matcher = new IgnoreClassloaderMatcher(Collections.singletonList(contextClassLoader.getClass().getName())); + + assertFalse(matcher.matches(null)); + + // url class loader not matches ignored class loaders + assertFalse(matcher.matches(URLClassLoader.class.getClassLoader())); + + // app class loader, matches ignored class loaders + assertTrue(matcher.matches(contextClassLoader), () -> "contextClassLoader: " + contextClassLoader.getClass().getName()); + + // matcher is null + assertFalse(matcher.matches(contextClassLoader.getParent())); + } +} diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredRawMatcherTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredRawMatcherTest.java new file mode 100644 index 000000000..359633e4e --- /dev/null +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredRawMatcherTest.java @@ -0,0 +1,48 @@ +package io.arex.inst.extension.matcher; + +import net.bytebuddy.description.type.TypeDescription; +import org.junit.jupiter.api.Test; + +import java.security.ProtectionDomain; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class IgnoredRawMatcherTest { + + @Test + void matches() { + ProtectionDomain protectionDomain = new ProtectionDomain(null, null); + TypeDescription.ForLoadedType typeDescription = new TypeDescription.ForLoadedType(String.class); + ClassLoader classLoader = IgnoredRawMatcherTest.class.getClassLoader(); + + // case 1 + // ignore type: false (classloader is null) + IgnoredRawMatcher matcher = new IgnoredRawMatcher(Collections.emptyList(), Collections.emptyList()); + assertFalse(matcher.matches(typeDescription, null, null, null, protectionDomain)); + + // case 2 + // ignore type: false + // ignore classloader: false + matcher = new IgnoredRawMatcher(Collections.emptyList(), Collections.emptyList()); + assertFalse(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + + // case 3 + // ignore type: true + // ignore classloader: false + matcher = new IgnoredRawMatcher(Collections.singletonList(typeDescription.getActualName()), Collections.emptyList()); + assertTrue(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + + // case 4 + // ignore type: false + // ignore classloader: true + matcher = new IgnoredRawMatcher(Collections.emptyList(), Collections.singletonList(classLoader.getClass().getName())); + assertTrue(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + + // case 3 + // ignore type: true + // ignore classloader: true + assertTrue(matcher.matches(typeDescription, classLoader, null, null, protectionDomain)); + } +} diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java index 4ef8ec4ec..e5218078c 100644 --- a/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/extension/matcher/IgnoredTypesMatcherTest.java @@ -3,13 +3,15 @@ import net.bytebuddy.description.type.TypeDescription; import org.junit.jupiter.api.Test; +import java.util.Collections; + import static org.junit.jupiter.api.Assertions.*; class IgnoredTypesMatcherTest { @Test void matches() { - IgnoredTypesMatcher matcher = new IgnoredTypesMatcher(); + IgnoredTypesMatcher matcher = new IgnoredTypesMatcher(Collections.emptyList()); assertTrue(matcher.matches(new TypeDescription.ForLoadedType(IgnoredTypesMatcher.class))); assertFalse(matcher.matches(new TypeDescription.ForLoadedType(String.class))); } diff --git a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java index 9c01f3e7f..b970d8169 100644 --- a/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java +++ b/arex-instrumentation-foundation/src/main/java/io/arex/foundation/config/ConfigManager.java @@ -53,6 +53,8 @@ public class ConfigManager { private EnumSet allowDayOfWeeks; private LocalTime allowTimeOfDayFrom; private LocalTime allowTimeOfDayTo; + private List ignoreTypePrefixes; + private List ignoreClassLoaderPrefixes; private List disabledModules; private List retransformModules; private Set excludeServiceOperations; @@ -263,6 +265,8 @@ void init() { setAllowDayOfWeeks(Integer.parseInt(System.getProperty(ALLOW_DAY_WEEKS, "127"))); setAllowTimeOfDayFrom(System.getProperty(ALLOW_TIME_FROM, "00:01")); setAllowTimeOfDayTo(System.getProperty(ALLOW_TIME_TO, "23:59")); + setIgnoreTypePrefixes(System.getProperty(IGNORED_TYPE_PREFIXES)); + setIgnoreClassLoaderPrefixes(System.getProperty(IGNORED_CLASS_LOADER_PREFIXES)); setDisabledModules(System.getProperty(DISABLE_MODULE)); setRetransformModules(System.getProperty(RETRANSFORM_MODULE, "dynamic-class")); setExcludeServiceOperations(System.getProperty(EXCLUDE_SERVICE_OPERATION)); @@ -500,6 +504,30 @@ private long nextWorkTime() { return Duration.between(LocalDateTime.now(), nextTime).toMillis(); } + public List getIgnoreTypePrefixes() { + return ignoreTypePrefixes; + } + + private void setIgnoreTypePrefixes(String ignoredTypes) { + if (StringUtil.isEmpty(ignoredTypes)) { + this.ignoreTypePrefixes = Collections.emptyList(); + } else { + this.ignoreTypePrefixes = Arrays.asList(StringUtil.split(ignoredTypes, ',')); + } + } + + public List getIgnoreClassLoaderPrefixes() { + return ignoreClassLoaderPrefixes; + } + + private void setIgnoreClassLoaderPrefixes(String ignoreClassLoaderPrefixes) { + if (StringUtil.isEmpty(ignoreClassLoaderPrefixes)) { + this.ignoreClassLoaderPrefixes = Collections.emptyList(); + } else { + this.ignoreClassLoaderPrefixes = Arrays.asList(StringUtil.split(ignoreClassLoaderPrefixes, ',')); + } + } + public List getDisabledModules() { return disabledModules; } From a61784bdd16ed788b50aa2b93798494589ddace6 Mon Sep 17 00:00:00 2001 From: DanLi39 <147678474+DanLi39@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:15:44 +0800 Subject: [PATCH 18/20] fix: add response attachments on service enter (#518) fix: add response attachments on service enter --- .../inst/dubbo/apache/v3/DubboProviderExtractor.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java b/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java index 27eb1b21f..cd8dcc47f 100644 --- a/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java +++ b/arex-instrumentation/dubbo/arex-dubbo-apache-v3/src/main/java/io/arex/inst/dubbo/apache/v3/DubboProviderExtractor.java @@ -32,12 +32,12 @@ public static void onServiceEnter(Invoker invoker, Invocation invocation) { addAttachmentsToContext(adapter); RequestHandlerManager.handleAfterCreateContext(invocation.getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); invocation.getAttributes().put(ArexConstants.ORIGINAL_REQUEST, Serializer.serialize(invocation.getArguments())); + setResponseHeader((k, v) -> RpcContext.getServerContext().setAttachment(k, v)); } public static void onServiceExit(Invoker invoker, Invocation invocation, Result result) { if (!ContextManager.needRecordOrReplay()) { return; } - setResponseHeader((k, v) -> setAttachment(invocation, k, v)); DubboAdapter adapter = DubboAdapter.of(invoker, invocation); RequestHandlerManager.postHandle(invocation.getAttachments(), RpcContext.getServerContext().getAttachments(), MockCategoryType.DUBBO_PROVIDER.getName()); @@ -54,12 +54,4 @@ private static Mocker makeMocker(DubboAdapter adapter) { responseAttributes.put(KEY_HEADERS, RpcContext.getServerContext().getObjectAttachments()); return buildMocker(mocker, adapter, requestAttributes, responseAttributes); } - - private static void setAttachment(Invocation invocation, String key, String value) { - RpcContext.getServerContext().setAttachment(key, value); - if (invocation instanceof RpcInvocation) { - RpcInvocation rpcInvocation = (RpcInvocation) invocation; - rpcInvocation.setAttachment(key, value); - } - } } From 14687f74e71dc0f127dd3bf459223812b2fbf965 Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:37:57 +0800 Subject: [PATCH 19/20] feat: support comparable runnable (#517) --- .../agent/bootstrap/ctx/RunnableWrapper.java | 50 ++++++++++++ .../bootstrap/ctx/RunnableWrapperTest.java | 79 ++++++++++++++++++- 2 files changed, 127 insertions(+), 2 deletions(-) diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/ctx/RunnableWrapper.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/ctx/RunnableWrapper.java index 5ace6643e..2afa2f1c0 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/ctx/RunnableWrapper.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/ctx/RunnableWrapper.java @@ -2,10 +2,16 @@ import io.arex.agent.bootstrap.TraceContextManager; +import java.lang.reflect.Field; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.atomic.AtomicBoolean; public class RunnableWrapper implements Runnable { + private static final ConcurrentHashMap> COMPARABLE_RUNNABLE_FIELD_MAP = new ConcurrentHashMap<>(); + private static final AtomicBoolean EXECEPTION_LOGGED = new AtomicBoolean(false); private final Runnable runnable; private final TraceTransmitter traceTransmitter; @@ -48,6 +54,50 @@ public static Runnable get(Runnable runnable) { if (runnable instanceof RunnableWrapper || runnable instanceof ForkJoinTask) { return runnable; } + + if (runnable instanceof Comparable) { + wrapRunnableField(runnable); + return runnable; + } return new RunnableWrapper(runnable); } + + /** + * issue: https://github.com/arextest/arex-agent-java/issues/516 + * executor with PriorityQueue + * ex: class PriorityRunnable extends Runnable implements Comparable { + * private final Runnable run; + * private final int priority; + * } + * can't wrap the PriorityRunnable because queue need runnable implement Comparable, + * so we need to wrap the original runnable field. + */ + private static void wrapRunnableField(Runnable runnable) { + try { + Class runnableClass = runnable.getClass(); + Optional originalRunnableFieldOpt = COMPARABLE_RUNNABLE_FIELD_MAP.computeIfAbsent(runnableClass.getName(), k -> { + for (Field declaredField : runnableClass.getDeclaredFields()) { + Class declaredFieldType = declaredField.getType(); + if (declaredFieldType.isAssignableFrom(Runnable.class) && !declaredFieldType.isAssignableFrom(Comparable.class)) { + declaredField.setAccessible(true); + return Optional.of(declaredField); + } + } + return Optional.empty(); + }); + if (originalRunnableFieldOpt.isPresent()) { + Field originalRunnableField = originalRunnableFieldOpt.get(); + Runnable originalRunnable = (Runnable) originalRunnableField.get(runnable); + if (originalRunnable instanceof RunnableWrapper) { + return; + } + RunnableWrapper runnableWrapper = new RunnableWrapper(originalRunnable); + originalRunnableField.set(runnable, runnableWrapper); + } + } catch (Exception e) { + if (EXECEPTION_LOGGED.compareAndSet(false, true)) { + System.err.printf("wrap original runnable %s failed.%n", runnable.getClass().getName()); + } + } + } } diff --git a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/ctx/RunnableWrapperTest.java b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/ctx/RunnableWrapperTest.java index 6c55068ea..90c2a4622 100644 --- a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/ctx/RunnableWrapperTest.java +++ b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/ctx/RunnableWrapperTest.java @@ -3,6 +3,9 @@ import io.arex.agent.bootstrap.TraceContextManager; import org.junit.jupiter.api.Test; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RunnableFuture; @@ -11,7 +14,7 @@ class RunnableWrapperTest { @Test - void get() { + void get() throws Exception { assertNull(RunnableWrapper.get(null)); TraceContextManager.set("mock"); Runnable objectRunnable = RunnableWrapper.get(new RunnableTest<>()); @@ -20,7 +23,29 @@ void get() { assertDoesNotThrow(emptyRunnable::run); assertNotNull(emptyRunnable.toString()); assertTrue(emptyRunnable.hashCode() > 0); - assertFalse(emptyRunnable.equals(objectRunnable)); + assertNotEquals(emptyRunnable, objectRunnable); + + NoRunnableFieldPriorityRunnable noRunnableFieldComparableRunnable = new NoRunnableFieldPriorityRunnable(1); + RunnableWrapper.get(noRunnableFieldComparableRunnable); + Field comparableRunnableFieldMap = RunnableWrapper.class.getDeclaredField("COMPARABLE_RUNNABLE_FIELD_MAP"); + comparableRunnableFieldMap.setAccessible(true); + Map> map = (Map>) comparableRunnableFieldMap.get(null); + assertNotNull(map); + assertFalse(map.get(noRunnableFieldComparableRunnable.getClass().getName()).isPresent()); + Runnable originalRunnable = () -> {}; + PriorityRunnable priorityRunnable = new PriorityRunnable(originalRunnable, 1); + Runnable runnable2 = RunnableWrapper.get(priorityRunnable); + assertSame(priorityRunnable, runnable2); + assertEquals("run", map.get(priorityRunnable.getClass().getName()).get().getName()); + assertInstanceOf(RunnableWrapper.class, priorityRunnable.getRun()); + // runnable field already wrapped + RunnableWrapper.get(priorityRunnable); + Runnable run = priorityRunnable.getRun(); + assertInstanceOf(RunnableWrapper.class, run); + Field runnable = RunnableWrapper.class.getDeclaredField("runnable"); + runnable.setAccessible(true); + assertFalse(runnable.get(run) instanceof RunnableWrapper); + TraceContextManager.remove(); } @@ -30,4 +55,54 @@ public final void setRawResult(T v) {} public final boolean exec() { return true; } public final void run() {} } + + public static class NoRunnableFieldPriorityRunnable implements Runnable, Comparable { + private final int priority; + + public NoRunnableFieldPriorityRunnable(int priority) { + this.priority = priority; + } + + @Override + public int compareTo(NoRunnableFieldPriorityRunnable other) { + int res = 0; + if (this.priority != other.priority) { + res = this.priority > other.priority ? -1 : 1; + } + return res; + } + + @Override + public void run() { + System.out.println("PriorityRunnable.run"); + } + } + + static class PriorityRunnable implements Runnable, Comparable { + private final Runnable run; + private final int priority; + + public PriorityRunnable(Runnable run, int priority) { + this.run = run; + this.priority = priority; + } + + @Override + public int compareTo(PriorityRunnable other) { + int res = 0; + if (this.priority != other.priority) { + res = this.priority > other.priority ? -1 : 1; + } + return res; + } + + @Override + public void run() { + this.run.run(); + } + + public Runnable getRun() { + return run; + } + } } From d0cf4f6961fee6a9edddee0f7e5a82f8479e4401 Mon Sep 17 00:00:00 2001 From: YongwuHe <38196495+YongwuHe@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:32:50 +0800 Subject: [PATCH 20/20] feat: support com.ning.httpclient (#512) --- README.md | 6 +- .../agent/bootstrap/util/CollectionUtil.java | 17 ++ .../arex/agent/bootstrap/util/StringUtil.java | 8 + .../bootstrap/util/CollectionUtilTest.java | 13 ++ .../agent/bootstrap/util/StringUtilTest.java | 24 ++- arex-agent/pom.xml | 5 + .../runtime}/listener/DirectExecutor.java | 2 +- .../runtime/listener/DirectExecutorTest.java | 18 +++ .../listener/ListenableFutureAdapter.java | 1 + .../common/HttpResponseWrapper.java | 20 ++- .../httpclient/arex-httpclient-ning/pom.xml | 28 ++++ .../AsyncHttpClientModuleInstrumentation.java | 21 +++ .../ning/AsyncHttpClientInstrumentation.java | 81 ++++++++++ .../ning/NingHttpClientAdapter.java | 112 +++++++++++++ .../ning/ResponseFutureListener.java | 38 +++++ .../ning/ResponseFutureWrapper.java | 56 +++++++ .../inst/httpclient/ning/ResponseWrapper.java | 151 ++++++++++++++++++ ...ncHttpClientModuleInstrumentationTest.java | 28 ++++ .../AsyncHttpClientInstrumentationTest.java | 105 ++++++++++++ .../ning/NingHttpClientAdapterTest.java | 145 +++++++++++++++++ .../ning/ResponseFutureListenerTest.java | 31 ++++ .../ning/ResponseFutureWrapperTest.java | 55 +++++++ .../httpclient/ning/ResponseWrapperTest.java | 133 +++++++++++++++ arex-instrumentation/pom.xml | 1 + 24 files changed, 1091 insertions(+), 8 deletions(-) rename {arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common => arex-instrumentation-api/src/main/java/io/arex/inst/runtime}/listener/DirectExecutor.java (85%) create mode 100644 arex-instrumentation-api/src/test/java/io/arex/inst/runtime/listener/DirectExecutorTest.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/pom.xml create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentation.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentation.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/NingHttpClientAdapter.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureListener.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureWrapper.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseWrapper.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentationTest.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentationTest.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/NingHttpClientAdapterTest.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureListenerTest.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureWrapperTest.java create mode 100644 arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseWrapperTest.java diff --git a/README.md b/README.md index f167fa2fc..ffcbe7184 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,11 @@ AREX utilizes the advanced Java technique, Instrument API, and is capable of ins #### Http Client - Apache HttpClient [4.0,) - OkHttp [3.0, 4.11] -- Spring WebClient [5.0,) -- Spring Template - Feign [9.0,) +- Spring OpenFeign +- Spring RestTemplate +- Spring WebClient [5.0,) +- [ning/async-http-client](https://github.com/ning/async-http-client) - Elasticsearch Client [7.x,) #### Redis Client - RedisTemplate diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java index 34acd1fe8..448b15a62 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/CollectionUtil.java @@ -87,4 +87,21 @@ public static List filterNull(List originalList) { } return filterList; } + + public static byte[] listToByteArray(List list) { + int totalLength = 0; + for (byte[] array : list) { + totalLength += array.length; + } + + byte[] result = new byte[totalLength]; + + int currentPos = 0; + for (byte[] array : list) { + System.arraycopy(array, 0, result, currentPos, array.length); + currentPos += array.length; + } + + return result; + } } diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/StringUtil.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/StringUtil.java index 7ba87699b..a61bb4544 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/StringUtil.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/util/StringUtil.java @@ -593,6 +593,14 @@ public static Set splitToSet(String str, char separatorChars) { return new HashSet<>(Arrays.asList(strs)); } + public static List splitToList(String str, char separatorChars) { + if (isEmpty(str)) { + return Collections.emptyList(); + } + String[] strs = split(str, separatorChars); + return Arrays.asList(strs); + } + public static boolean isNumeric(final String cs) { if (isEmpty(cs)) { return false; diff --git a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java index f62a54010..1e99d2146 100644 --- a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java +++ b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/CollectionUtilTest.java @@ -77,4 +77,17 @@ void filterNull() { actualResult = CollectionUtil.filterNull(CollectionUtil.newArrayList("mock")); assertEquals(1, actualResult.size()); } + + @Test + void testListToByteArray() { + List list = new ArrayList<>(); + list.add(new byte[]{1, 2, 3}); + list.add(new byte[]{4, 5, 6}); + list.add(new byte[]{7, 8, 9}); + + byte[] result = CollectionUtil.listToByteArray(list); + + byte[] expected = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9}; + assertArrayEquals(expected, result); + } } diff --git a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/StringUtilTest.java b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/StringUtilTest.java index 40addc919..c32344826 100644 --- a/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/StringUtilTest.java +++ b/arex-agent-bootstrap/src/test/java/io/arex/agent/bootstrap/util/StringUtilTest.java @@ -2,10 +2,8 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -324,6 +322,24 @@ void testSplitToSet() { assertTrue(set.contains("c")); } + @Test + void testSplitToList() { + // null string + List nullSet = StringUtil.splitToList(null, ','); + assertEquals(0, nullSet.size()); + + // empty string + List emptySet = StringUtil.splitToList("", ','); + assertEquals(0, emptySet.size()); + + String s = "aaa,bb,c"; + List set = StringUtil.splitToList(s, ','); + assertEquals(3, set.size()); + assertTrue(set.contains("aaa")); + assertTrue(set.contains("bb")); + assertTrue(set.contains("c")); + } + @Test void testIsNumeric() { String s = "123"; diff --git a/arex-agent/pom.xml b/arex-agent/pom.xml index f0b37ba78..680d06507 100644 --- a/arex-agent/pom.xml +++ b/arex-agent/pom.xml @@ -141,6 +141,11 @@ arex-httpclient-feign ${project.version} + + ${project.groupId} + arex-httpclient-ning + ${project.version} + ${project.groupId} arex-netty-v3 diff --git a/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/listener/DirectExecutor.java b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/listener/DirectExecutor.java similarity index 85% rename from arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/listener/DirectExecutor.java rename to arex-instrumentation-api/src/main/java/io/arex/inst/runtime/listener/DirectExecutor.java index a9a0e40e4..e844acb8b 100644 --- a/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/listener/DirectExecutor.java +++ b/arex-instrumentation-api/src/main/java/io/arex/inst/runtime/listener/DirectExecutor.java @@ -1,4 +1,4 @@ -package io.arex.inst.dynamic.common.listener; +package io.arex.inst.runtime.listener; import java.util.concurrent.Executor; diff --git a/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/listener/DirectExecutorTest.java b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/listener/DirectExecutorTest.java new file mode 100644 index 000000000..688291028 --- /dev/null +++ b/arex-instrumentation-api/src/test/java/io/arex/inst/runtime/listener/DirectExecutorTest.java @@ -0,0 +1,18 @@ +package io.arex.inst.runtime.listener; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.Executor; + +import static org.junit.jupiter.api.Assertions.*; + +class DirectExecutorTest { + + @Test + void execute() { + Executor executor = DirectExecutor.INSTANCE; + final boolean[] ran = {false}; + executor.execute(() -> ran[0] = true); + assertTrue(ran[0]); + } +} diff --git a/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/listener/ListenableFutureAdapter.java b/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/listener/ListenableFutureAdapter.java index 589c590d4..c9363693b 100644 --- a/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/listener/ListenableFutureAdapter.java +++ b/arex-instrumentation/dynamic/arex-dynamic-common/src/main/java/io/arex/inst/dynamic/common/listener/ListenableFutureAdapter.java @@ -5,6 +5,7 @@ import com.google.common.util.concurrent.ListenableFuture; import io.arex.inst.dynamic.common.DynamicClassExtractor; +import io.arex.inst.runtime.listener.DirectExecutor; public class ListenableFutureAdapter { diff --git a/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpResponseWrapper.java b/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpResponseWrapper.java index 9d9a84d20..c74a75965 100644 --- a/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpResponseWrapper.java +++ b/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpResponseWrapper.java @@ -9,6 +9,24 @@ public class HttpResponseWrapper { private StringTuple locale; private List headers; private String reason; + private int statusCode; + private String typeName; + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } public String getReason() { return reason; @@ -91,4 +109,4 @@ public String getS() { return s; } } -} \ No newline at end of file +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/pom.xml b/arex-instrumentation/httpclient/arex-httpclient-ning/pom.xml new file mode 100644 index 000000000..9f5764b18 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/pom.xml @@ -0,0 +1,28 @@ + + + + io.arex + arex-instrumentation-parent + ${revision} + ../../pom.xml + + 4.0.0 + arex-httpclient-ning + + + ${project.groupId} + arex-httpclient-common + ${project.version} + compile + + + com.ning + async-http-client + 1.9.39 + provided + + + + diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentation.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentation.java new file mode 100644 index 000000000..4363f41cb --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentation.java @@ -0,0 +1,21 @@ +package io.arex.inst.httpclient; + +import com.google.auto.service.AutoService; +import io.arex.inst.extension.ModuleInstrumentation; +import io.arex.inst.extension.TypeInstrumentation; +import io.arex.inst.httpclient.ning.AsyncHttpClientInstrumentation; + +import java.util.Collections; +import java.util.List; + +@AutoService(ModuleInstrumentation.class) +public class AsyncHttpClientModuleInstrumentation extends ModuleInstrumentation { + public AsyncHttpClientModuleInstrumentation() { + super("arex-httpclient-ning"); + } + + @Override + public List instrumentationTypes() { + return Collections.singletonList(new AsyncHttpClientInstrumentation()); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentation.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentation.java new file mode 100644 index 000000000..ab2b02535 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentation.java @@ -0,0 +1,81 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.ListenableFuture; +import com.ning.http.client.Request; +import com.ning.http.client.Response; +import io.arex.agent.bootstrap.model.MockResult; +import io.arex.inst.extension.MethodInstrumentation; +import io.arex.inst.extension.TypeInstrumentation; +import io.arex.inst.httpclient.common.HttpClientAdapter; +import io.arex.inst.httpclient.common.HttpClientExtractor; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.context.RepeatedCollectManager; +import io.arex.inst.runtime.listener.DirectExecutor; +import io.arex.inst.runtime.util.IgnoreUtils; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; + +import java.util.Collections; +import java.util.List; + +import static net.bytebuddy.matcher.ElementMatchers.*; + +public class AsyncHttpClientInstrumentation extends TypeInstrumentation { + @Override + protected ElementMatcher typeMatcher() { + return named("com.ning.http.client.AsyncHttpClient"); + } + + @Override + public List methodAdvices() { + return Collections.singletonList(new MethodInstrumentation( + isMethod().and(named("executeRequest").and(takesArguments(2))), ExecuteRequestAdvice.class.getName())); + } + + public static class ExecuteRequestAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class, skipOn = Advice.OnNonDefaultValue.class) + public static boolean onEnter(@Advice.Argument(0) Request request, + @Advice.Local("mockResult") MockResult mockResult, + @Advice.Local("extractor") HttpClientExtractor extractor){ + if (IgnoreUtils.excludeOperation(request.getUri().getPath())) { + return false; + } + if (ContextManager.needRecord()) { + RepeatedCollectManager.enter(); + } + if (ContextManager.needRecordOrReplay()) { + HttpClientAdapter adapter = new NingHttpClientAdapter(request); + extractor = new HttpClientExtractor<>(adapter); + if (ContextManager.needReplay()) { + mockResult = extractor.replay(); + return mockResult != null && mockResult.notIgnoreMockResult(); + } + } + return false; + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + public static void onExit(@Advice.Local("mockResult") MockResult mockResult, + @Advice.Local("extractor") HttpClientExtractor extractor, + @Advice.Return(readOnly = false) ListenableFuture responseFuture, + @Advice.Thrown(readOnly = false) Throwable throwable) { + if (mockResult != null && mockResult.notIgnoreMockResult()) { + if (mockResult.getThrowable() != null) { + throwable = mockResult.getThrowable(); + } else { + responseFuture = new ResponseFutureWrapper(mockResult.getResult()); + } + return; + } + if (ContextManager.needRecord() && RepeatedCollectManager.exitAndValidate() && extractor != null) { + if (throwable != null) { + extractor.record(throwable); + } else { + responseFuture.addListener(new ResponseFutureListener(extractor, responseFuture), DirectExecutor.INSTANCE); + } + } + } + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/NingHttpClientAdapter.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/NingHttpClientAdapter.java new file mode 100644 index 000000000..2a9ed59a4 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/NingHttpClientAdapter.java @@ -0,0 +1,112 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.FluentCaseInsensitiveStringsMap; +import com.ning.http.client.Request; +import com.ning.http.client.Response; +import io.arex.agent.bootstrap.util.CollectionUtil; +import io.arex.agent.bootstrap.util.StringUtil; +import io.arex.inst.httpclient.common.HttpClientAdapter; +import io.arex.inst.httpclient.common.HttpResponseWrapper; +import io.arex.inst.runtime.log.LogManager; +import io.arex.inst.runtime.serializer.Serializer; +import io.arex.inst.runtime.util.TypeUtil; + +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +public class NingHttpClientAdapter implements HttpClientAdapter { + private final Request request; + + public NingHttpClientAdapter(Request request) { + this.request = request; + } + + @Override + public String getMethod() { + return request.getMethod(); + } + + @Override + public byte[] getRequestBytes() { + if (request.getByteData() != null) { + return request.getByteData(); + } + + if (request.getStringData() != null) { + return Base64.getDecoder().decode(request.getStringData()); + } + + if (request.getCompositeByteData() != null) { + return CollectionUtil.listToByteArray(request.getCompositeByteData()); + } + + return ZERO_BYTE; + } + + @Override + public String getRequestContentType() { + return getRequestHeader("Content-Type"); + } + + @Override + public String getRequestHeader(String name) { + return request.getHeaders().getFirstValue(name); + } + + @Override + public URI getUri() { + try { + return request.getUri().toJavaNetURI(); + } catch (Exception e) { + return null; + } + } + + /** + * users can process data and return custom types by implementing a custom AsyncHandler. + * So it needs to be compatible with this situation. + * default handler returns Response type. + */ + @Override + public HttpResponseWrapper wrap(Object response) { + HttpResponseWrapper httpResponseWrapper = new HttpResponseWrapper(); + try { + if (response instanceof Response) { + Response tempResponse = (Response) response; + httpResponseWrapper.setStatusLine(tempResponse.getStatusText()); + httpResponseWrapper.setContent(tempResponse.getResponseBodyAsBytes()); + httpResponseWrapper.setHeaders(buildHeaders(tempResponse.getHeaders())); + httpResponseWrapper.setStatusCode(tempResponse.getStatusCode()); + return httpResponseWrapper; + } + String responseString = Serializer.serialize(response); + httpResponseWrapper.setContent(StringUtil.isEmpty(responseString) ? + ZERO_BYTE : responseString.getBytes(StandardCharsets.UTF_8)); + httpResponseWrapper.setTypeName(TypeUtil.getName(response)); + return httpResponseWrapper; + } catch (Exception e) { + LogManager.warn("ning.wrap", e); + return httpResponseWrapper; + } + } + + private List buildHeaders(FluentCaseInsensitiveStringsMap headers) { + List headerList = new ArrayList<>(headers.size()); + for (Map.Entry> entry : headers.entrySet()) { + headerList.add(new HttpResponseWrapper.StringTuple(entry.getKey(), StringUtil.join(entry.getValue(), ","))); + } + return headerList; + } + + @Override + public Object unwrap(HttpResponseWrapper wrapped) { + if (StringUtil.isNotEmpty(wrapped.getTypeName())) { + return Serializer.deserialize(new String(wrapped.getContent(), StandardCharsets.UTF_8), wrapped.getTypeName()); + } + return new ResponseWrapper(wrapped); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureListener.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureListener.java new file mode 100644 index 000000000..2dff83bda --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureListener.java @@ -0,0 +1,38 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.ListenableFuture; +import com.ning.http.client.Request; +import io.arex.agent.bootstrap.ctx.TraceTransmitter; +import io.arex.inst.httpclient.common.HttpClientExtractor; +import io.arex.inst.runtime.log.LogManager; + +import java.util.concurrent.TimeUnit; + + +public class ResponseFutureListener implements Runnable { + private final ListenableFuture responseFuture; + private final HttpClientExtractor extractor; + private final TraceTransmitter transmitter; + public ResponseFutureListener(HttpClientExtractor extractor, ListenableFuture responseFuture) { + this.responseFuture = responseFuture; + this.extractor = extractor; + this.transmitter = TraceTransmitter.create(); + } + + /** + * responseFuture is com.ning.http.client.ListenableFuture, + * only Future.get() to get the response. + * only when the responseFuture is done, run will be called, record response. + */ + @Override + public void run() { + try (TraceTransmitter transmit = transmitter.transmit()){ + extractor.record(responseFuture.get(1, TimeUnit.SECONDS)); + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + LogManager.warn("ResponseFutureListener.run", interruptedException); + } catch (Exception exception) { + LogManager.warn("ResponseFutureListener.run", exception); + } + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureWrapper.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureWrapper.java new file mode 100644 index 000000000..565793052 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseFutureWrapper.java @@ -0,0 +1,56 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.listenable.AbstractListenableFuture; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class ResponseFutureWrapper extends AbstractListenableFuture { + private final Object response; + + public ResponseFutureWrapper(Object response) { + this.response = response; + } + + @Override + public void done() { + runListeners(); + } + + @Override + public void abort(Throwable t) { + runListeners(); + } + + @Override + public void touch() { + // do nothing + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + runListeners(); + return true; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public Object get() throws InterruptedException, ExecutionException { + return this.response; + } + + @Override + public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return get(); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseWrapper.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseWrapper.java new file mode 100644 index 000000000..820c612f9 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/main/java/io/arex/inst/httpclient/ning/ResponseWrapper.java @@ -0,0 +1,151 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.FluentCaseInsensitiveStringsMap; +import com.ning.http.client.Response; +import com.ning.http.client.cookie.Cookie; +import com.ning.http.client.cookie.CookieDecoder; +import com.ning.http.client.uri.Uri; +import io.arex.agent.bootstrap.util.MapUtils; +import io.arex.agent.bootstrap.util.StringUtil; +import io.arex.inst.httpclient.common.HttpResponseWrapper; +import org.jboss.netty.handler.codec.http.HttpHeaders; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.*; + +public class ResponseWrapper implements Response { + private final String statusLine; + private final byte[] content; + private final FluentCaseInsensitiveStringsMap headers; + private final int statusCode; + + public ResponseWrapper(HttpResponseWrapper httpResponseWrapper) { + this.statusLine = httpResponseWrapper.getStatusLine(); + this.content = httpResponseWrapper.getContent(); + List wrapperHeaders = httpResponseWrapper.getHeaders(); + this.headers = new FluentCaseInsensitiveStringsMap(); + for (HttpResponseWrapper.StringTuple header : wrapperHeaders) { + headers.put(header.getF(), StringUtil.splitToList(header.getS(), ',')); + } + this.statusCode = httpResponseWrapper.getStatusCode(); + } + @Override + public int getStatusCode() { + return this.statusCode; + } + + @Override + public String getStatusText() { + return this.statusLine; + } + + @Override + public byte[] getResponseBodyAsBytes() throws IOException { + return this.content; + } + + @Override + public ByteBuffer getResponseBodyAsByteBuffer() throws IOException { + return ByteBuffer.wrap(this.content); + } + + @Override + public InputStream getResponseBodyAsStream() throws IOException { + return new ByteArrayInputStream(this.content); + } + + @Override + public String getResponseBodyExcerpt(int maxLength, String charset) throws IOException { + String response = getResponseBody(charset); + return response.length() <= maxLength ? response : response.substring(0, maxLength); + } + + @Override + public String getResponseBody(String charset) throws IOException { + if (charset == null) { + return getResponseBody(); + } + return new String(this.content, charset); + } + + @Override + public String getResponseBodyExcerpt(int maxLength) throws IOException { + return getResponseBodyExcerpt(maxLength, null); + } + + @Override + public String getResponseBody() throws IOException { + return new String(this.content); + } + + @Override + public Uri getUri() { + return null; + } + + @Override + public String getContentType() { + return headers != null ? getHeader("Content-Type") : null; + } + + @Override + public String getHeader(String name) { + return headers != null ? getHeaders().getFirstValue(name) : null; + } + + @Override + public List getHeaders(String name) { + return this.headers.get(name); + } + + @Override + public FluentCaseInsensitiveStringsMap getHeaders() { + return this.headers; + } + + @Override + public boolean isRedirected() { + switch (this.statusCode) { + case 301: + case 302: + case 303: + case 307: + case 308: + return true; + default: + return false; + } + } + + @Override + public List getCookies() { + List cookies = new ArrayList<>(); + for (Map.Entry> header : this.headers.entrySet()) { + if (header.getKey().equalsIgnoreCase(HttpHeaders.Names.SET_COOKIE)) { + List v = header.getValue(); + for (String value : v) { + cookies.add(CookieDecoder.decode(value)); + } + } + } + return cookies; + } + + @Override + public boolean hasResponseStatus() { + return this.statusCode != 0; + } + + @Override + public boolean hasResponseHeaders() { + return MapUtils.isNotEmpty(this.headers); + } + + @Override + public boolean hasResponseBody() { + return this.content != null && this.content.length > 0; + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentationTest.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentationTest.java new file mode 100644 index 000000000..4e579a046 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/AsyncHttpClientModuleInstrumentationTest.java @@ -0,0 +1,28 @@ +package io.arex.inst.httpclient; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; + +class AsyncHttpClientModuleInstrumentationTest { + private static AsyncHttpClientModuleInstrumentation target = null; + + @BeforeAll + static void setUp() { + target = new AsyncHttpClientModuleInstrumentation(); + } + + @AfterAll + static void tearDown() { + target = null; + Mockito.clearAllCaches(); + } + + @Test + void instrumentationTypes() { + assertEquals(1, target.instrumentationTypes().size()); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentationTest.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentationTest.java new file mode 100644 index 000000000..1a4e25142 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/AsyncHttpClientInstrumentationTest.java @@ -0,0 +1,105 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.ListenableFuture; +import com.ning.http.client.Request; +import com.ning.http.client.uri.Uri; +import io.arex.agent.bootstrap.model.MockResult; +import io.arex.agent.bootstrap.model.Mocker; +import io.arex.inst.httpclient.common.HttpClientExtractor; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.context.RepeatedCollectManager; +import io.arex.inst.runtime.listener.DirectExecutor; +import io.arex.inst.runtime.util.IgnoreUtils; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +class AsyncHttpClientInstrumentationTest { + private static AsyncHttpClientInstrumentation target = null; + + @BeforeAll + static void setUp() { + target = new AsyncHttpClientInstrumentation(); + Mockito.mockStatic(IgnoreUtils.class); + Mockito.mockStatic(ContextManager.class); + Mockito.mockStatic(RepeatedCollectManager.class); + } + + @AfterAll + static void tearDown() { + target = null; + Mockito.clearAllCaches(); + } + + @Test + void typeMatcher() { + assertTrue(target.typeMatcher().matches(TypeDescription.ForLoadedType.of(AsyncHttpClient.class))); + } + + @Test + void methodAdvices() { + assertEquals(1, target.methodAdvices().size()); + } + + @Test + void onEnter() { + Request mockRequest = Mockito.mock(Request.class); + Uri uriMock = Mockito.mock(Uri.class); + MockResult mockResult = MockResult.success("testMockResult"); + Mockito.when(mockRequest.getUri()).thenReturn(uriMock); + // match ignore + Mockito.when(IgnoreUtils.excludeOperation(Mockito.any())).thenReturn(true); + assertFalse(AsyncHttpClientInstrumentation.ExecuteRequestAdvice.onEnter(mockRequest, null, null)); + // not match ignore + Mockito.when(IgnoreUtils.excludeOperation(Mockito.any())).thenReturn(false); + // record + Mockito.when(ContextManager.needRecord()).thenReturn(true); + Mockito.when(ContextManager.needRecordOrReplay()).thenReturn(true); + Mockito.when(ContextManager.needReplay()).thenReturn(false); + assertFalse(AsyncHttpClientInstrumentation.ExecuteRequestAdvice.onEnter(mockRequest, null, null)); + // replay + Mockito.when(ContextManager.needReplay()).thenReturn(true); + Mockito.when(ContextManager.needRecord()).thenReturn(false); + try (MockedConstruction mockedConstruction = Mockito.mockConstruction(HttpClientExtractor.class, + (mock, context) -> Mockito.doReturn(mockResult).when(mock).replay())) { + assertTrue(AsyncHttpClientInstrumentation.ExecuteRequestAdvice.onEnter(mockRequest, null, null)); + } + } + + @Test + void onExit() { + Mockito.when(ContextManager.needRecord()).thenReturn(true); + Mockito.when(RepeatedCollectManager.exitAndValidate()).thenReturn(true); + // replay result + MockResult mockResult = MockResult.success("testMockResult"); + HttpClientExtractor extractorMock = Mockito.mock(HttpClientExtractor.class); + AsyncHttpClientInstrumentation.ExecuteRequestAdvice.onExit( mockResult, extractorMock, null, null); + Mockito.verify(extractorMock, Mockito.never()).record(any()); + // replay throwable + mockResult = MockResult.success(new RuntimeException()); + AsyncHttpClientInstrumentation.ExecuteRequestAdvice.onExit( mockResult, extractorMock, null, null); + Mockito.verify(extractorMock, Mockito.never()).record(any()); + + ListenableFuture listenableFutureMock = Mockito.mock(ListenableFuture.class); + // record future + try (MockedConstruction mockedConstruction = Mockito.mockConstruction(ResponseFutureListener.class)) { + AsyncHttpClientInstrumentation.ExecuteRequestAdvice.onExit(null, extractorMock, listenableFutureMock, null); + Mockito.verify(listenableFutureMock, Mockito.times(1)).addListener(mockedConstruction.constructed().get(0), DirectExecutor.INSTANCE); + } + + // record throwable + RuntimeException runtimeException = new RuntimeException("test"); + AsyncHttpClientInstrumentation.ExecuteRequestAdvice.onExit(null, extractorMock, listenableFutureMock, runtimeException); + Mockito.verify(extractorMock, Mockito.times(1)).record(runtimeException); + } + +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/NingHttpClientAdapterTest.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/NingHttpClientAdapterTest.java new file mode 100644 index 000000000..4a2715e7e --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/NingHttpClientAdapterTest.java @@ -0,0 +1,145 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.FluentCaseInsensitiveStringsMap; +import com.ning.http.client.Request; +import com.ning.http.client.Response; +import com.ning.http.client.uri.Uri; +import io.arex.inst.httpclient.common.HttpResponseWrapper; +import io.arex.inst.runtime.serializer.Serializer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class NingHttpClientAdapterTest { + private static Request request = null; + + @BeforeAll + static void setUp() { + request = mock(Request.class); + when(request.getMethod()).thenReturn("GET"); + when(request.getBodyEncoding()).thenReturn("application/json"); + when(request.getHeaders()).thenReturn(new FluentCaseInsensitiveStringsMap().add("Content-Type", "application/json")); + mockStatic(Serializer.class); + } + + @AfterAll + static void tearDown() { + request = null; + Mockito.clearAllCaches(); + } + + + @Test + void getMethod() { + NingHttpClientAdapter ningHttpClientAdapter = new NingHttpClientAdapter(request); + assertEquals("GET", ningHttpClientAdapter.getMethod()); + } + + @Test + void getRequestBytes() { + // byteData != null + when(request.getByteData()).thenReturn("test".getBytes()); + NingHttpClientAdapter ningHttpClientAdapter = new NingHttpClientAdapter(request); + assertArrayEquals("test".getBytes(), ningHttpClientAdapter.getRequestBytes()); + + // stringData != null + when(request.getStringData()).thenReturn("test"); + assertArrayEquals("test".getBytes(), ningHttpClientAdapter.getRequestBytes()); + + // compositeByteData != null + when(request.getByteData()).thenReturn(null); + when(request.getStringData()).thenReturn(null); + when(request.getCompositeByteData()).thenReturn(Arrays.asList("test1".getBytes(), "test2".getBytes())); + assertEquals("test1test2", new String(ningHttpClientAdapter.getRequestBytes())); + + // all null + when(request.getCompositeByteData()).thenReturn(null); + assertArrayEquals(new byte[0], ningHttpClientAdapter.getRequestBytes()); + } + + @Test + void getRequestContentType() { + NingHttpClientAdapter ningHttpClientAdapter = new NingHttpClientAdapter(request); + assertEquals("application/json", ningHttpClientAdapter.getRequestContentType()); + } + + @Test + void getRequestHeader() { + NingHttpClientAdapter ningHttpClientAdapter = new NingHttpClientAdapter(request); + assertEquals("application/json", ningHttpClientAdapter.getRequestHeader("Content-Type")); + } + + @Test + void getUri() { + // normal url + when(request.getUri()).thenReturn(new Uri("http", null, "localhost", 8080, "/test", null)); + NingHttpClientAdapter ningHttpClientAdapter = new NingHttpClientAdapter(request); + assertEquals(URI.create("http://localhost:8080/test"), ningHttpClientAdapter.getUri()); + + // exception + when(request.getUri()).thenReturn(null); + assertDoesNotThrow(ningHttpClientAdapter::getUri); + } + + @Test + void wrap() throws IOException { + Response response = mock(Response.class); + when(response.getStatusText()).thenReturn("OK"); + when(response.getResponseBodyAsBytes()).thenReturn("test".getBytes()); + when(response.getStatusCode()).thenReturn(200); + FluentCaseInsensitiveStringsMap map = new FluentCaseInsensitiveStringsMap(); + map.add("key", "value1"); + map.add("key", "value2"); + map.add("Content-Type", "application/json"); + when(response.getHeaders()).thenReturn(map); + NingHttpClientAdapter ningHttpClientAdapter = new NingHttpClientAdapter(mock(Request.class)); + HttpResponseWrapper httpResponseWrapper = ningHttpClientAdapter.wrap(response); + assertEquals("OK", httpResponseWrapper.getStatusLine()); + assertArrayEquals("test".getBytes(), httpResponseWrapper.getContent()); + assertEquals(200, httpResponseWrapper.getStatusCode()); + for (HttpResponseWrapper.StringTuple header : httpResponseWrapper.getHeaders()) { + if ("key".equals(header.getF())) { + assertEquals("value1,value2", header.getS()); + } + } + assertNull(httpResponseWrapper.getTypeName()); + + // response is not com.ning.http.client.Response + when(Serializer.serialize(any())).thenReturn("test"); + httpResponseWrapper = ningHttpClientAdapter.wrap("test"); + assertEquals(httpResponseWrapper.getTypeName(), String.class.getName()); + + // exception + when(Serializer.serialize(any())).thenThrow(new RuntimeException()); + httpResponseWrapper = ningHttpClientAdapter.wrap(httpResponseWrapper); + assertNotNull(httpResponseWrapper); + assertNull(httpResponseWrapper.getContent()); + } + + @Test + void unwrap() { + HttpResponseWrapper httpResponseWrapper = mock(HttpResponseWrapper.class); + NingHttpClientAdapter ningHttpClientAdapter = new NingHttpClientAdapter(mock(Request.class)); + // httpResponseWrapper responseTypeName is not empty + when(httpResponseWrapper.getTypeName()).thenReturn(String.class.getName()); + byte[] bytes = "test".getBytes(StandardCharsets.UTF_8); + when(httpResponseWrapper.getContent()).thenReturn(bytes); + when(Serializer.deserialize("test", String.class.getName())).thenReturn("test"); + Object response = ningHttpClientAdapter.unwrap(httpResponseWrapper); + assertEquals("test", response); + // httpResponseWrapper responseTypeName is empty + when(httpResponseWrapper.getTypeName()).thenReturn(null); + response = ningHttpClientAdapter.unwrap(httpResponseWrapper); + assertInstanceOf(ResponseWrapper.class, response); + } + +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureListenerTest.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureListenerTest.java new file mode 100644 index 000000000..17b0c969a --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureListenerTest.java @@ -0,0 +1,31 @@ +package io.arex.inst.httpclient.ning; + +import com.ning.http.client.ListenableFuture; +import io.arex.inst.httpclient.common.HttpClientExtractor; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +class ResponseFutureListenerTest { + + @Test + void run() throws ExecutionException, InterruptedException, TimeoutException { + HttpClientExtractor extractor = mock(HttpClientExtractor.class); + ListenableFuture responseFuture = mock(ListenableFuture.class); + + when(responseFuture.get(1, TimeUnit.SECONDS)).thenReturn("Test"); + ResponseFutureListener responseFutureListener = new ResponseFutureListener(extractor, responseFuture); + responseFutureListener.run(); + + verify(extractor, times(1)).record("Test"); + // exception + when(responseFuture.get(1, TimeUnit.SECONDS)).thenThrow(new InterruptedException()); + assertDoesNotThrow(responseFutureListener::run); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureWrapperTest.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureWrapperTest.java new file mode 100644 index 000000000..b8bf077ee --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseFutureWrapperTest.java @@ -0,0 +1,55 @@ +package io.arex.inst.httpclient.ning; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.*; + +class ResponseFutureWrapperTest { + @Test + void done() { + ResponseFutureWrapper responseFutureWrapper = new ResponseFutureWrapper("Test"); + responseFutureWrapper.done(); + assertTrue(responseFutureWrapper.isDone()); + } + + @Test + void abort() { + ResponseFutureWrapper responseFutureWrapper = new ResponseFutureWrapper("Test"); + responseFutureWrapper.abort(new Throwable()); + assertTrue(responseFutureWrapper.isDone()); + } + + @Test + void cancel() { + ResponseFutureWrapper responseFutureWrapper = new ResponseFutureWrapper("Test"); + assertTrue(responseFutureWrapper.cancel(true)); + } + + @Test + void isCancelled() { + ResponseFutureWrapper responseFutureWrapper = new ResponseFutureWrapper("Test"); + assertFalse(responseFutureWrapper.isCancelled()); + } + + @Test + void isDone() { + ResponseFutureWrapper responseFutureWrapper = new ResponseFutureWrapper("Test"); + assertTrue(responseFutureWrapper.isDone()); + } + + @Test + void get() throws ExecutionException, InterruptedException { + ResponseFutureWrapper responseFutureWrapper = new ResponseFutureWrapper("Test"); + assertEquals("Test", responseFutureWrapper.get()); + } + + @Test + void getWithTimeout() throws ExecutionException, InterruptedException, TimeoutException { + ResponseFutureWrapper responseFutureWrapper = new ResponseFutureWrapper("Test"); + assertEquals("Test", responseFutureWrapper.get(1, java.util.concurrent.TimeUnit.SECONDS)); + } + +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseWrapperTest.java b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseWrapperTest.java new file mode 100644 index 000000000..1766f6f30 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-ning/src/test/java/io/arex/inst/httpclient/ning/ResponseWrapperTest.java @@ -0,0 +1,133 @@ +package io.arex.inst.httpclient.ning; + +import io.arex.inst.httpclient.common.HttpResponseWrapper; +import org.jboss.netty.handler.codec.http.HttpHeaders; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class ResponseWrapperTest { + private static ResponseWrapper responseWrapper = null; + + @BeforeAll + static void setUp() { + HttpResponseWrapper httpResponseWrapper = new HttpResponseWrapper(); + httpResponseWrapper.setContent("Test content".getBytes()); + httpResponseWrapper.setStatusLine("OK"); + httpResponseWrapper.setStatusCode(200); + List list = getStringTuples(); + httpResponseWrapper.setHeaders(list); + responseWrapper = new ResponseWrapper(httpResponseWrapper); + } + + private static List getStringTuples() { + List list = new ArrayList<>(); + HttpResponseWrapper.StringTuple stringTuple = new HttpResponseWrapper.StringTuple("Content-Type", "application/json"); + HttpResponseWrapper.StringTuple stringTuple1 = new HttpResponseWrapper.StringTuple("Content-Length", "100"); + HttpResponseWrapper.StringTuple cookies = new HttpResponseWrapper.StringTuple(HttpHeaders.Names.SET_COOKIE, "test1=1, test2=2"); + list.add(stringTuple); + list.add(stringTuple1); + list.add(cookies); + return list; + } + + @AfterAll + static void tearDown() { + responseWrapper = null; + } + + @Test + void getStatusCode() { + assertEquals(200, responseWrapper.getStatusCode()); + } + + @Test + void getStatusText() { + assertEquals("OK", responseWrapper.getStatusText()); + } + + @Test + void getResponseBodyAsBytes() throws IOException { + assertArrayEquals("Test content".getBytes(), responseWrapper.getResponseBodyAsBytes()); + } + + @Test + void getResponseBodyAsByteBuffer() throws IOException { + assertEquals("Test content", new String(responseWrapper.getResponseBodyAsByteBuffer().array())); + } + + @Test + void getResponseBodyAsStream() throws IOException { + byte[] bytes = new byte[responseWrapper.getResponseBodyAsStream().available()]; + responseWrapper.getResponseBodyAsStream().read(bytes); + assertEquals("Test content", new String(bytes)); + } + + @Test + void getResponseBodyExcerpt() throws IOException { + assertEquals("Test", responseWrapper.getResponseBodyExcerpt(4)); + } + + @Test + void getResponseBody() throws IOException { + assertEquals("Test content", responseWrapper.getResponseBody()); + } + + @Test + void getResponseBodyExcerptWithCharset() throws IOException { + assertEquals("Test", responseWrapper.getResponseBodyExcerpt(4, "UTF-8")); + } + + @Test + void getUri() { + assertNull(responseWrapper.getUri()); + } + + @Test + void getContentType() { + assertEquals("application/json", responseWrapper.getContentType()); + } + + @Test + void getHeader() { + assertEquals("application/json", responseWrapper.getHeader("Content-Type")); + } + + @Test + void getHeaders() { + assertEquals(3, responseWrapper.getHeaders().size()); + assertEquals("application/json", responseWrapper.getHeaders("Content-Type").get(0)); + } + + @Test + void isRedirected() { + assertFalse(responseWrapper.isRedirected()); + } + + @Test + void getCookies() { + assertEquals(2, responseWrapper.getCookies().size()); + } + + @Test + void hasResponseStatus() { + assertTrue(responseWrapper.hasResponseStatus()); + } + + @Test + void hasResponseHeaders() { + assertTrue(responseWrapper.hasResponseHeaders()); + } + + @Test + void hasResponseBody() { + assertTrue(responseWrapper.hasResponseBody()); + } + +} diff --git a/arex-instrumentation/pom.xml b/arex-instrumentation/pom.xml index be2210f66..b36d2b256 100644 --- a/arex-instrumentation/pom.xml +++ b/arex-instrumentation/pom.xml @@ -44,6 +44,7 @@ httpclient/arex-httpclient-webclient-v5 httpclient/arex-httpclient-resttemplate httpclient/arex-httpclient-feign + httpclient/arex-httpclient-ning netty/arex-netty-v3 netty/arex-netty-v4 dubbo/arex-dubbo-apache-v2