diff --git a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/MockCategoryType.java b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/MockCategoryType.java index eb29e60bb..d6ee98e5e 100644 --- a/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/MockCategoryType.java +++ b/arex-agent-bootstrap/src/main/java/io/arex/agent/bootstrap/model/MockCategoryType.java @@ -19,6 +19,8 @@ public class MockCategoryType implements Serializable { public static final MockCategoryType DUBBO_CONSUMER = createDependency("DubboConsumer"); public static final MockCategoryType DUBBO_PROVIDER = createEntryPoint("DubboProvider"); public static final MockCategoryType DUBBO_STREAM_PROVIDER = createDependency("DubboStreamProvider"); + public static final MockCategoryType MQTT_MESSAGE_CONSUMER = createEntryPoint("MqttMessageConsumer"); + private String name; private boolean entryPoint; diff --git a/arex-agent/pom.xml b/arex-agent/pom.xml index 968510c40..02f314815 100644 --- a/arex-agent/pom.xml +++ b/arex-agent/pom.xml @@ -147,6 +147,11 @@ arex-jcasbin ${project.version} + + ${project.groupId} + arex-integration-mqtt + ${project.version} + 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 b29da4d89..3f398249b 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 @@ -68,6 +68,11 @@ public static ArexMocker createDubboStreamProvider(String operationName) { return create(MockCategoryType.DUBBO_STREAM_PROVIDER, operationName); } + public static ArexMocker createMqttConsumer(String operationName){ + return create(MockCategoryType.MQTT_MESSAGE_CONSUMER,operationName); + } + + public static ArexMocker create(MockCategoryType categoryType, String operationName) { ArexMocker mocker = new ArexMocker(); long createTime = System.currentTimeMillis(); diff --git a/arex-instrumentation/mq/arex-integration-mqtt/pom.xml b/arex-instrumentation/mq/arex-integration-mqtt/pom.xml new file mode 100644 index 000000000..92bf444c5 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/pom.xml @@ -0,0 +1,23 @@ + + + + arex-instrumentation-parent + io.arex + ${revision} + ../../pom.xml + + 4.0.0 + + arex-integration-mqtt + + + + org.springframework + spring-messaging + 5.3.4 + provided + + + diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/MQTTAdapterHelper.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/MQTTAdapterHelper.java new file mode 100644 index 000000000..622a5e87c --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/MQTTAdapterHelper.java @@ -0,0 +1,83 @@ +package io.arex.inst.mqtt; + +import io.arex.agent.bootstrap.internal.Pair; +import io.arex.agent.bootstrap.util.StringUtil; +import io.arex.inst.mqtt.adapter.MessageAdapter; +import io.arex.inst.runtime.config.Config; +import io.arex.inst.runtime.listener.CaseEvent; +import io.arex.inst.runtime.listener.CaseEventDispatcher; +import io.arex.inst.runtime.listener.EventSource; +import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.util.IgnoreUtils; + +/** + * MQTTAdapterHelper + */ +public class MQTTAdapterHelper { + + public static final String PROCESSED_FLAG = "arex-processed-flag"; + + + public static Pair onServiceEnter(MessageAdapter adapter, Object messageChannel, Object message) { + Msg msg = adapter.warpMessage(message); + if (msg == null) { + return null; + } + if (adapter.markProcessed(msg, PROCESSED_FLAG)) { + return null; + } + MC mc = adapter.warpMC(messageChannel); + if (mc == null){ + return null; + } + CaseEventDispatcher.onEvent(CaseEvent.ofEnterEvent()); + if (shouldSkip(adapter, mc, msg)) { + return null; + } + String caseId = adapter.getHeader(mc, msg, ArexConstants.RECORD_ID); + String excludeMockTemplate = adapter.getHeader(mc, msg, ArexConstants.HEADER_EXCLUDE_MOCK); + CaseEventDispatcher.onEvent(CaseEvent.ofCreateEvent(EventSource.of(caseId, excludeMockTemplate))); + return Pair.of(mc,msg); + } + + + public static void onServiceExit(MessageAdapter adapter, Object messageChannel, Object message){ + Msg msg = adapter.warpMessage(message); + MC mc = adapter.warpMC(messageChannel); + if (msg == null || mc == null) { + return; + } + adapter.removeHeader(mc,msg,PROCESSED_FLAG); + new MessageQueueExtractor<>( mc, msg,adapter).execute(); + } + + + + + private static boolean shouldSkip(MessageAdapter adapter,MC mc, Msg msg){ + String caseId = adapter.getHeader(mc, msg, ArexConstants.RECORD_ID); + // Replay scene + if (StringUtil.isNotEmpty(caseId)) { + return Config.get().getBoolean("arex.disable.replay", false); + } + + String forceRecord = adapter.getHeader(mc, msg, ArexConstants.FORCE_RECORD); + // Do not skip if header with arex-force-record=true + if (Boolean.parseBoolean(forceRecord)) { + return false; + } + // Skip if request header with arex-replay-warm-up=true + if (Boolean.parseBoolean(adapter.getHeader(mc, msg, ArexConstants.REPLAY_WARM_UP))) { + return true; + } + String topic = adapter.getHeader(mc, msg, ArexConstants.REPLAY_WARM_UP); + if (StringUtil.isEmpty(topic)) { + return false; + } + if (IgnoreUtils.ignoreOperation(topic)) { + return true; + } + return Config.get().invalidRecord(topic); + } + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/MessageQueueExtractor.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/MessageQueueExtractor.java new file mode 100644 index 000000000..fd2c79fce --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/MessageQueueExtractor.java @@ -0,0 +1,78 @@ +package io.arex.inst.mqtt; + +import io.arex.agent.bootstrap.model.Mocker; +import io.arex.inst.mqtt.adapter.MessageAdapter; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.util.LogUtil; +import io.arex.inst.runtime.util.MockUtils; +import org.springframework.messaging.MessageHeaders; + +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author : MentosL + * @date : 2023/5/9 22:16 + */ +public class MessageQueueExtractor { + private final MC messageChannel; + private final Msg message; + private final MessageAdapter adapter; + + + public MessageQueueExtractor(MC messageChannel, Msg message, MessageAdapter adapter) { + this.messageChannel = messageChannel; + this.message = message; + this.adapter = adapter; + } + + + public void execute() { + try { + if (message == null || messageChannel == null || adapter == null) { + return; + } + if (!ContextManager.needRecordOrReplay()) { + return; + } + executeBeforeProcess(); + doExecute(); + executeAfterProcess(); + } catch (Exception e) { + LogUtil.warn("MessageQueue.execute", e); + } + } + + private void executeBeforeProcess() { + if (ContextManager.needRecord()) { + adapter.addHeader(messageChannel,message, ArexConstants.RECORD_ID,ContextManager.currentContext().getCaseId()); + } + if (ContextManager.needReplay()) { + adapter.addHeader(messageChannel,message, ArexConstants.REPLAY_ID,ContextManager.currentContext().getReplayId()); + } + } + private void executeAfterProcess(){ + // Think about other ways to replace the head + adapter.resetMsg(message); + } + + private void doExecute() { + Mocker mocker = MockUtils.createMqttConsumer(adapter.getHeader(messageChannel,message,"mqtt_receivedTopic")); + MessageHeaders header = adapter.getHeader(messageChannel, message); + Map requestOrigin = new HashMap<>(); + for (Map.Entry entry : header.entrySet()) { + requestOrigin.put(entry.getKey(), entry.getValue()); + } + Map requestAttributes = Collections.singletonMap("Headers", requestOrigin); + mocker.getTargetRequest().setAttributes(requestAttributes); + mocker.getTargetRequest().setBody(Base64.getEncoder().encodeToString(adapter.getMsg(messageChannel,message))); + if (ContextManager.needReplay()) { + MockUtils.replayMocker(mocker); + } else if (ContextManager.needRecord()) { + MockUtils.recordMocker(mocker); + } + } +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/adapter/MessageAdapter.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/adapter/MessageAdapter.java new file mode 100644 index 000000000..c573aadf7 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/adapter/MessageAdapter.java @@ -0,0 +1,28 @@ +package io.arex.inst.mqtt.adapter; + +import org.springframework.messaging.MessageHeaders; + +/** + * MessageAdapter + */ +public interface MessageAdapter { + + MC warpMC(Object messageChannel); + + Msg warpMessage(Object message); + + byte[] getMsg(MC c, Msg msg); + + MessageHeaders getHeader(MC c, Msg msg); + + boolean markProcessed(Msg msg,String flagKey); + + String getHeader(MC mc,Msg msg,String key); + + boolean removeHeader(MC mc,Msg msg,String key); + + boolean addHeader(MC mc,Msg msg,String key,String value); + + Msg resetMsg(Msg msg); + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/adapter/MessageAdapterImpl.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/adapter/MessageAdapterImpl.java new file mode 100644 index 000000000..79bd509fb --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/adapter/MessageAdapterImpl.java @@ -0,0 +1,166 @@ +package io.arex.inst.mqtt.adapter; + +import io.arex.agent.bootstrap.util.StringUtil; +import io.arex.inst.mqtt.warp.GenericMessageWarp; +import io.arex.inst.mqtt.warp.MessageHeaderWarp; +import io.arex.inst.runtime.util.LogUtil; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.GenericMessage; + +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; + +/** + * MessageImpl + */ +public class MessageAdapterImpl implements MessageAdapter { + + private static final MessageAdapterImpl INSTANCE = new MessageAdapterImpl(); + + public static MessageAdapterImpl getInstance() { + return INSTANCE; + } + + @Override + public byte[] getMsg(MessageChannel messageChannel, Message msg) { + if (msg == null){ + return new byte[]{}; + } + Object payload = msg.getPayload(); + if (payload == null){ + return new byte[]{}; + } + if (payload instanceof byte[]){ + return ((byte[]) payload); + } + return payload.toString().getBytes(StandardCharsets.UTF_8); + } + + @Override + public MessageChannel warpMC(Object messageChannel) { + if (messageChannel == null){ + return null; + } + if (messageChannel instanceof MessageChannel){ + return (MessageChannel) messageChannel; + } + return null; + } + + @Override + public Message warpMessage(Object message) { + if (message == null){ + return null; + } + if (message instanceof GenericMessageWarp){ + return (GenericMessageWarp) message; + } + + if (message instanceof GenericMessage) { + GenericMessage messageTemp = (GenericMessage) message; + MessageHeaders headers = messageTemp.getHeaders(); + MessageHeaderWarp messageHeaderWarp = new MessageHeaderWarp(headers); + return new GenericMessageWarp(messageTemp.getPayload(), messageHeaderWarp); + } + if (message instanceof Message){ + return (Message)message; + } + return null; + } + + @Override + public MessageHeaders getHeader(MessageChannel messageChannel, Message msg) { + if (msg == null){ + return null; + } + if (msg instanceof GenericMessageWarp){ + GenericMessageWarp messageTemp = (GenericMessageWarp) msg; + return messageTemp.getMessageHeaderWarp(); + } + return msg.getHeaders(); + } + + @Override + public boolean markProcessed(Message message, String flagKey) { + if (message == null){ + return true; + } + if (message instanceof GenericMessageWarp){ + GenericMessageWarp genericMessageWarp = (GenericMessageWarp)message; + genericMessageWarp.put(flagKey,Boolean.TRUE.toString()); + } + return false; + } + + @Override + public String getHeader(MessageChannel messageChannel, Message message, String key) { + if (message == null || StringUtil.isEmpty(key)){ + return null; + } + if (message instanceof GenericMessageWarp) { + GenericMessageWarp genericMessageWarp = (GenericMessageWarp) message; + Object object = genericMessageWarp.get(key); + return object != null ? object.toString() : null; + } + + if(message instanceof GenericMessage){ + Object obj = message.getHeaders().get(key); + return obj != null ? obj.toString() : null; + } + if (message.getHeaders() != null){ + Object obj = message.getHeaders().get(key); + return obj != null ? obj.toString() : null ; + } + return null; + } + + @Override + public boolean removeHeader(MessageChannel messageChannel, Message message, String key) { + if (message == null || StringUtil.isEmpty(key)){ + return false; + } + if (message instanceof GenericMessageWarp){ + GenericMessageWarp genericMessageWarp = (GenericMessageWarp) message; + genericMessageWarp.removeHeader(key); + return true; + } + return false; + } + + @Override + public boolean addHeader(MessageChannel messageChannel, Message message, String key, String value) { + if (message == null ){ + return false; + } + if (message instanceof GenericMessageWarp){ + GenericMessageWarp genericMessageWarp = (GenericMessageWarp) message; + genericMessageWarp.put(key,value); + return true; + } + return false; + } + + @Override + public Message resetMsg(Message message) { + if (message == null){ + return null; + } + if (message instanceof GenericMessageWarp){ + try { + GenericMessageWarp messageWarp = (GenericMessageWarp) message; + Field headers = message.getClass().getSuperclass().getDeclaredField("headers"); + headers.setAccessible(true); + headers.set(message, messageWarp.getMessageHeaderWarp()); + } catch (NoSuchFieldException e) { + LogUtil.warn("MessageAdapterImpl.resetMsg - NoSuchFieldException", e); + } catch (IllegalAccessException e) { + LogUtil.warn("MessageAdapterImpl.resetMsg - IllegalAccessException", e); + } + } + return message; + } + + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/inst/MQGenericInstrumentationV3.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/inst/MQGenericInstrumentationV3.java new file mode 100644 index 000000000..92656bc56 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/inst/MQGenericInstrumentationV3.java @@ -0,0 +1,66 @@ +package io.arex.inst.mqtt.inst; + +import io.arex.agent.bootstrap.internal.Pair; +import io.arex.inst.extension.MethodInstrumentation; +import io.arex.inst.extension.TypeInstrumentation; +import io.arex.inst.mqtt.MQTTAdapterHelper; +import io.arex.inst.mqtt.adapter.MessageAdapterImpl; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +import java.util.Collections; +import java.util.List; + +import static io.arex.inst.extension.matcher.SafeExtendsClassMatcher.extendsClass; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +/** + * EclipseInstrumentationV3 + */ +public class MQGenericInstrumentationV3 extends TypeInstrumentation { + @Override + public ElementMatcher typeMatcher() { + return extendsClass(named("org.springframework.messaging.core.AbstractMessageSendingTemplate"), false) + .and(named("org.springframework.messaging.core.GenericMessagingTemplate")); + } + + @Override + public List methodAdvices() { + ElementMatcher matcher = named("doSend") + .and(takesArgument(0, named("org.springframework.messaging.MessageChannel"))) + .and(takesArgument(1, named("org.springframework.messaging.Message"))); + return Collections.singletonList(new MethodInstrumentation(matcher, ArrivedAdvice.class.getName())); + } + + public static class ArrivedAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.Argument(value = 0, readOnly = false) MessageChannel messageChannel, + @Advice.Argument(value = 1, readOnly = false) Message message, + @Advice.Local("channelMsgPair") Pair pair) { + Pair messageChannelMessagePair = + MQTTAdapterHelper.onServiceEnter(MessageAdapterImpl.getInstance(), messageChannel, message); + if (messageChannelMessagePair == null){ + return; + } + pair = messageChannelMessagePair; + } + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Argument(value = 0, readOnly = false) MessageChannel messageChannel, + @Advice.Argument(value = 1, readOnly = false) Message message, + @Advice.Local("channelMsgPair") Pair pair) { + if (pair != null){ + message = pair.getSecond(); + } + MQTTAdapterHelper.onServiceExit(MessageAdapterImpl.getInstance(), messageChannel, message); + } + + } + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/inst/MQTTAdapterModuleInstrumentation.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/inst/MQTTAdapterModuleInstrumentation.java new file mode 100644 index 000000000..6c3aaa6ed --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/inst/MQTTAdapterModuleInstrumentation.java @@ -0,0 +1,24 @@ +package io.arex.inst.mqtt.inst; + +import com.google.auto.service.AutoService; +import io.arex.inst.extension.ModuleInstrumentation; +import io.arex.inst.extension.TypeInstrumentation; + +import java.util.Arrays; +import java.util.List; + +/** + * MQTTAdapterModuleInstrumentation + */ +@AutoService(ModuleInstrumentation.class) +public class MQTTAdapterModuleInstrumentation extends ModuleInstrumentation { + + public MQTTAdapterModuleInstrumentation() { + super("mqtt"); + } + + @Override + public List instrumentationTypes() { + return Arrays.asList(new MQGenericInstrumentationV3()); + } +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/warp/GenericMessageWarp.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/warp/GenericMessageWarp.java new file mode 100644 index 000000000..7b509411c --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/warp/GenericMessageWarp.java @@ -0,0 +1,53 @@ +package io.arex.inst.mqtt.warp; + +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.GenericMessage; + +import java.util.Map; + +/** + * @author : MentosL + * @date : 2023/5/10 20:53 + */ +public class GenericMessageWarp extends GenericMessage { + + private MessageHeaderWarp messageHeaderWarp; + + public GenericMessageWarp(Object payload) { + super(payload); + this.messageHeaderWarp = new MessageHeaderWarp(super.getHeaders()); + } + + public GenericMessageWarp(Object payload, Map headers) { + super(payload, headers); + this.messageHeaderWarp = new MessageHeaderWarp(headers); + } + + public GenericMessageWarp(Object payload, MessageHeaders headers) { + super(payload, headers); + this.messageHeaderWarp = new MessageHeaderWarp(headers); + } + + public void removeHeader(String key) { + if (this.messageHeaderWarp != null){ + this.messageHeaderWarp.remove(key); + } + } + + public void put(String key, String value) { + if (this.messageHeaderWarp != null) { + this.messageHeaderWarp.put(key, value); + } + } + + public Object get(String key){ + if (this.messageHeaderWarp != null) { + return this.messageHeaderWarp.get(key); + } + return getHeaders().get(key); + } + + public MessageHeaderWarp getMessageHeaderWarp() { + return messageHeaderWarp; + } +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/warp/MessageHeaderWarp.java b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/warp/MessageHeaderWarp.java new file mode 100644 index 000000000..a8284bfa4 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/main/java/io/arex/inst/mqtt/warp/MessageHeaderWarp.java @@ -0,0 +1,47 @@ +package io.arex.inst.mqtt.warp; + +import org.springframework.messaging.MessageHeaders; + +import java.util.Map; +import java.util.UUID; + +/** + * @author : MentosL + * @date : 2023/5/9 23:12 + */ +public class MessageHeaderWarp extends MessageHeaders { + + + public MessageHeaderWarp(MessageHeaders messageHeaders) { + super(messageHeaders); + if (messageHeaders != null && messageHeaders.size() > 0){ + if (messageHeaders.get(ID) != null){ + this.put(ID,messageHeaders.get(ID)); + } + if (messageHeaders.get(TIMESTAMP) != null){ + this.put(TIMESTAMP,messageHeaders.get(TIMESTAMP)); + } + } + } + + public MessageHeaderWarp(Map headers) { + super(headers); + } + + public MessageHeaderWarp(Map headers, UUID id, Long timestamp) { + super(headers, id, timestamp); + } + + public Object put(String key, Object value){ + if (value == null){ + return null; + } + super.getRawHeaders().put(key,value); + return value; + } + + public void remove(String key){ + super.getRawHeaders().remove(key); + } + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/MQTTAdapterHelperTest.java b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/MQTTAdapterHelperTest.java new file mode 100644 index 000000000..8b3aba92a --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/MQTTAdapterHelperTest.java @@ -0,0 +1,106 @@ +package io.arex.inst.mqtt.inst; + +import io.arex.agent.bootstrap.internal.Pair; +import io.arex.inst.mqtt.MQTTAdapterHelper; +import io.arex.inst.mqtt.adapter.MessageAdapter; +import io.arex.inst.mqtt.adapter.MessageAdapterImpl; +import io.arex.inst.runtime.context.ContextManager; +import io.arex.inst.runtime.context.RecordLimiter; +import io.arex.inst.runtime.model.ArexConstants; +import io.arex.inst.runtime.util.IgnoreUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +/** + * @author : MentosL + * @date : 2023/5/16 20:53 + */ +@ExtendWith(MockitoExtension.class) +public class MQTTAdapterHelperTest { + static MessageAdapter messageAdapter; + static MessageChannel messageChannel; + static Message message; + + @BeforeAll + static void setUp() { + messageAdapter = Mockito.mock(MessageAdapter.class); + messageChannel = Mockito.mock(MessageChannel.class); + message = Mockito.mock(Message.class); + } + + @AfterAll + static void tearDown() { + messageAdapter = null; + messageChannel = null; + message = null; + Mockito.clearAllCaches(); + } + + @ParameterizedTest + @MethodSource("onServiceEnterCase") + void onServiceEnter(Runnable mocker, Predicate predicate) { + mocker.run(); + Pair result = MQTTAdapterHelper.onServiceEnter(messageAdapter, new Object(), new Object()); + assertTrue(predicate.test(result)); + } + + static Stream onServiceEnterCase() { + Runnable emptyMocker = () -> {}; + Runnable mocker1 = () -> { + Mockito.when(messageAdapter.warpMessage(any())).thenReturn("mock"); + Mockito.when(messageAdapter.markProcessed(any(), any())).thenReturn(true); + }; + Runnable mocker2 = () -> { + Mockito.when(messageAdapter.markProcessed(any(), any())).thenReturn(false); + }; + Runnable mocker3 = () -> { + Mockito.when(messageAdapter.getMsg(any(),any())).thenReturn(null); + }; + + Runnable mocker4 = () -> { + Mockito.when(messageAdapter.getHeader(any(), any())).thenReturn(null); + }; + Runnable mocker5 = () -> { + Mockito.when(messageAdapter.getHeader(any(), any(),eq(ArexConstants.RECORD_ID))).thenReturn("mock"); + }; + Runnable mocker6 = () -> { + Mockito.when(messageAdapter.removeHeader(any(), any(),eq(ArexConstants.RECORD_ID))).thenReturn(true); + }; + Runnable mocker7 = () -> { + Mockito.when(messageAdapter.addHeader(any(), any(),eq(ArexConstants.RECORD_ID),eq("mock"))).thenReturn(true); + }; + Runnable mocker8 = () -> { + Mockito.when(messageAdapter.resetMsg(any())).thenReturn("mock"); + }; + Predicate> predicate1 = Objects::isNull; + return Stream.of( + arguments(emptyMocker, predicate1), + arguments(mocker1, predicate1), + arguments(mocker2, predicate1), + arguments(mocker3, predicate1), + arguments(mocker4, predicate1), + arguments(mocker5, predicate1), + arguments(mocker6, predicate1), + arguments(mocker7, predicate1), + arguments(mocker8, predicate1) + ); + } + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/MessageQueueExtractorTest.java b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/MessageQueueExtractorTest.java new file mode 100644 index 000000000..57856b682 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/MessageQueueExtractorTest.java @@ -0,0 +1,115 @@ +package io.arex.inst.mqtt.inst; + +import io.arex.agent.bootstrap.model.ArexMocker; +import io.arex.agent.bootstrap.model.Mocker; +import io.arex.inst.mqtt.MessageQueueExtractor; +import io.arex.inst.mqtt.adapter.MessageAdapter; +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.util.MockUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; + +import java.io.IOException; +import java.util.Collections; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.ArgumentMatchers.any; + +/** + * @author : MentosL + * @date : 2023/5/22 10:27 + */ +public class MessageQueueExtractorTest { + static MessageAdapter adapter; + static Message message; + static MessageChannel messageChannel; + + @BeforeAll + static void setUp() { + adapter = Mockito.mock(MessageAdapter.class); + message = Mockito.mock(Message.class); + messageChannel = Mockito.mock(MessageChannel.class); + Mockito.mockStatic(ContextManager.class); + Mockito.mockStatic(MockUtils.class); + } + + @AfterAll + static void tearDown() { + adapter = null; + message = null; + messageChannel = null; + Mockito.clearAllCaches(); + } + + + + @ParameterizedTest(name = "[{index}] {2}") + @MethodSource("executeCase") + void execute(String log, Runnable mock, Runnable verify) throws IOException { + mock.run(); + new MessageQueueExtractor<>(messageChannel,message, adapter).execute(); + assertDoesNotThrow(verify::run); + } + + + static Stream executeCase() { + Runnable mock1 = () -> Mockito.when(adapter.addHeader(messageChannel,message, ArexConstants.REPLAY_ID,"mock-replay-id")).thenReturn(true); + + Runnable verify1 = () -> { + try { + adapter.resetMsg(message); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + + Runnable mock2 = () -> { + Mockito.when(adapter.getHeader(messageChannel,message, ArexConstants.REPLAY_ID)).thenReturn(null); + Mockito.when(ContextManager.needRecordOrReplay()).thenReturn(false); + }; + + Runnable mock3 = () -> { + Mockito.when(adapter.getHeader(messageChannel,message, ArexConstants.REPLAY_ID)).thenReturn(null); + Mockito.when(ContextManager.needRecordOrReplay()).thenReturn(true); + + ArexMocker mocker = new ArexMocker(); + mocker.setTargetRequest(new Mocker.Target()); + mocker.setTargetResponse(new Mocker.Target()); + Mockito.when(MockUtils.createServlet(any())).thenReturn(mocker); + + Mockito.when(adapter.getMsg(messageChannel,message)).thenReturn(new byte[0]); + Mockito.when(ContextManager.needRecord()).thenReturn(true); + Mockito.when(ContextManager.currentContext()).thenReturn(ArexContext.of("mock-trace-id")); + }; + Runnable verify2 = () -> { + Mockito.verify(adapter).addHeader(messageChannel,message, ArexConstants.RECORD_ID, "mock-trace-id"); + }; + + Runnable mock4 = () -> { + Mockito.when(ContextManager.needRecord()).thenReturn(false); + Mockito.when(ContextManager.needReplay()).thenReturn(true); + }; + + Runnable verify3 = () -> { + Mockito.verify(adapter).addHeader(messageChannel,message, ArexConstants.REPLAY_ID, null); + }; + + return Stream.of( + arguments("response header contains arex trace", mock1, verify1), + arguments("no need record or replay", mock2, verify1), + arguments("record execute", mock3, verify2), + arguments("replay execute", mock4, verify3) + ); + } + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/adapter/impl/MessageAdapterTest.java b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/adapter/impl/MessageAdapterTest.java new file mode 100644 index 000000000..0e4a831a5 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/adapter/impl/MessageAdapterTest.java @@ -0,0 +1,109 @@ +package io.arex.inst.mqtt.inst.adapter.impl; + +import io.arex.inst.mqtt.adapter.MessageAdapterImpl; +import io.arex.inst.mqtt.warp.GenericMessageWarp; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.support.GenericMessage; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +/** + * @author : MentosL + * @date : 2023/5/11 16:27 + */ +public class MessageAdapterTest { + + MessageAdapterImpl instance = MessageAdapterImpl.getInstance(); + MessageChannel messageChannel = Mockito.mock(MessageChannel.class); + Message message = Mockito.mock(Message.class); + GenericMessage genericMessage = Mockito.mock(GenericMessage.class); + GenericMessageWarp genericMessageWarp = Mockito.mock(GenericMessageWarp.class); + + + @Test + void getInstance() { + assertNotNull(instance); + } + + @Test + void getMsg(){ + assertNotNull(instance.getMsg(messageChannel,message)); + assertTrue(instance.getMsg(messageChannel,message).length == 0); + assertInstanceOf(byte[].class,instance.getMsg(messageChannel,message)); + } + + @Test + void warpMC(){ + assertNotNull(instance.warpMC(messageChannel)); + assertInstanceOf(MessageChannel.class,instance.warpMC(messageChannel)); + assertNull(instance.warpMC(new Object())); + } + + @Test + void warpMessage(){ + assertNotNull(instance.warpMessage(message)); + assertNull(instance.warpMessage(null)); + assertInstanceOf(GenericMessageWarp.class,instance.warpMessage(genericMessageWarp)); + when(genericMessage.getPayload()).thenReturn(new Object()); + assertInstanceOf(GenericMessage.class,instance.warpMessage(genericMessage)); + } + + @Test + void getHeader(){ + assertNull(instance.getHeader(messageChannel,message,null)); + assertNull(instance.getHeader(messageChannel,message,"")); + Map temp = new HashMap<>(); + temp.put("mock-key","mock-value"); + when(genericMessage.getHeaders()).thenReturn(new MessageHeaders(temp)); + assertSame("mock-value",instance.getHeader(messageChannel,genericMessage,"mock-key")); + } + + @Test + void removeHeader(){ + assertFalse(instance.removeHeader(messageChannel,null,"mock-key")); + assertFalse(instance.removeHeader(messageChannel,genericMessageWarp,"")); + Map temp = new HashMap<>(); + temp.put("mock-key","mock-value"); + when(genericMessageWarp.getHeaders()).thenReturn(new MessageHeaders(temp)); + assertTrue(instance.removeHeader(messageChannel,genericMessageWarp,"mock-key")); + + assertFalse(instance.removeHeader(messageChannel,message,"mock-key")); + assertFalse(instance.removeHeader(messageChannel,genericMessage,"mock-key")); + } + + @Test + void addHeader(){ + assertFalse(instance.addHeader(messageChannel,null,"mock-key","mock-value")); + assertFalse(instance.addHeader(messageChannel,message,"mock-key","mock-value")); + assertFalse(instance.addHeader(messageChannel,genericMessage,"mock-key","mock-value")); + + when(genericMessageWarp.getHeaders()).thenReturn(new MessageHeaders(new HashMap<>())); + assertTrue(instance.addHeader(messageChannel,genericMessageWarp,"mock-key","mock-value")); + } + + @Test + void resetMsg(){ + assertNull(instance.resetMsg(null)); + + Map temp = new HashMap<>(); + temp.put("mock-key","mock-value"); + when(genericMessageWarp.getHeaders()).thenReturn(new MessageHeaders(temp)); + + assertNotNull(instance.resetMsg(genericMessageWarp)); + assertTrue(instance.resetMsg(genericMessageWarp).getHeaders().size() == 3); + assertSame("mock-value",instance.resetMsg(genericMessageWarp).getHeaders().get("mock-key")); + } +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/inst/MQGenericInstrumentationV3Test.java b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/inst/MQGenericInstrumentationV3Test.java new file mode 100644 index 000000000..62e30ea2c --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/inst/MQGenericInstrumentationV3Test.java @@ -0,0 +1,65 @@ +package io.arex.inst.mqtt.inst.inst; + + +import io.arex.agent.bootstrap.internal.Pair; +import io.arex.inst.mqtt.MQTTAdapterHelper; +import io.arex.inst.mqtt.inst.MQGenericInstrumentationV3; +import net.bytebuddy.description.type.TypeDescription; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.core.AbstractMessageSendingTemplate; +import org.springframework.messaging.core.GenericMessagingTemplate; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; + +public class MQGenericInstrumentationV3Test { + MQGenericInstrumentationV3 inst = new MQGenericInstrumentationV3(); + + + @BeforeAll + static void setUp() { + Mockito.mockStatic(MQTTAdapterHelper.class); + } + + @AfterAll + static void tearDown() { + Mockito.clearAllCaches(); + } + + + @Test + void typeMatcher() { + assertFalse(inst.typeMatcher().matches(TypeDescription.ForLoadedType.of(AbstractMessageSendingTemplate.class))); + assertTrue(inst.typeMatcher().matches(TypeDescription.ForLoadedType.of(GenericMessagingTemplate.class))); + } + + @Test + void methodAdvices() { + assertEquals(1, inst.methodAdvices().size()); + } + + + @Test + void ServiceAdvice_onEnter() { + Mockito.when(MQTTAdapterHelper.onServiceEnter(any(), any(), any())).thenReturn(null); + assertDoesNotThrow(() -> MQGenericInstrumentationV3.ArrivedAdvice.onEnter(null, null,null)); + + MessageChannel messageChannel = Mockito.mock(MessageChannel.class); + Message message = Mockito.mock(Message.class); + Mockito.when(MQTTAdapterHelper.onServiceEnter(any(), any(), any())).thenReturn(Pair.of(messageChannel, message)); + assertDoesNotThrow(() -> MQGenericInstrumentationV3.ArrivedAdvice.onEnter(null, null,null)); + } + + @Test + void ServiceAdvice_onExit() { + assertDoesNotThrow(() -> MQGenericInstrumentationV3.ArrivedAdvice.onExit(null, null,null)); + } +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/inst/MQTTAdapterModuleInstrumentationTest.java b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/inst/MQTTAdapterModuleInstrumentationTest.java new file mode 100644 index 000000000..76ecfcbe3 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/inst/MQTTAdapterModuleInstrumentationTest.java @@ -0,0 +1,30 @@ +package io.arex.inst.mqtt.inst.inst; + +import io.arex.inst.mqtt.inst.MQTTAdapterModuleInstrumentation; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author : MentosL + * @date : 2023/5/21 15:19 + */ +public class MQTTAdapterModuleInstrumentationTest { + MQTTAdapterModuleInstrumentation module = new MQTTAdapterModuleInstrumentation(); + + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void instrumentationTypes() { + assertEquals(1, module.instrumentationTypes().size()); + } +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/warp/GenericMessageWarpTest.java b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/warp/GenericMessageWarpTest.java new file mode 100644 index 000000000..760c7e7aa --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/warp/GenericMessageWarpTest.java @@ -0,0 +1,65 @@ +package io.arex.inst.mqtt.inst.warp; + +import io.arex.inst.mqtt.warp.GenericMessageWarp; +import org.junit.jupiter.api.Test; +import org.springframework.messaging.MessageHeaders; + +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author : MentosL + * @date : 2023/5/21 16:16 + */ +public class GenericMessageWarpTest { + + + @Test + void GenericMessageWarp1(){ + assertNotNull(new GenericMessageWarp(new Object())); + assertThrows(IllegalArgumentException.class,()-> new GenericMessageWarp(null)); + } + + @Test + void GenericMessageWarp2(){ + assertNotNull(new GenericMessageWarp(new Object(),new HashMap())); + assertThrows(IllegalArgumentException.class,()-> new GenericMessageWarp(null,new HashMap())); + } + + + @Test + void GenericMessageWarp3(){ + assertNotNull(new GenericMessageWarp(new Object(),new MessageHeaders(new HashMap<>()))); + assertThrows(IllegalArgumentException.class,()-> new GenericMessageWarp(null,new MessageHeaders(new HashMap<>()))); + assertThrows(IllegalArgumentException.class,()-> new GenericMessageWarp(new Object(),null)); + } + + @Test + void removeHeader(){ + GenericMessageWarp genericMessageWarp = new GenericMessageWarp(new Object()); + genericMessageWarp.removeHeader("id"); + assertEquals(1, genericMessageWarp.getMessageHeaderWarp().size()); + assertEquals(2, genericMessageWarp.getHeaders().size()); + } + + + @Test + void put(){ + GenericMessageWarp genericMessageWarp = new GenericMessageWarp(new Object()); + genericMessageWarp.put("mock-key","mock-value"); + assertEquals(3, genericMessageWarp.getMessageHeaderWarp().size()); + assertEquals(2, genericMessageWarp.getHeaders().size()); + } + + + @Test + void get(){ + GenericMessageWarp genericMessageWarp = new GenericMessageWarp(new Object()); + genericMessageWarp.put("mock-key","mock-value"); + assertEquals(3, genericMessageWarp.getMessageHeaderWarp().size()); + assertEquals(2, genericMessageWarp.getHeaders().size()); + assertSame("mock-value",genericMessageWarp.get("mock-key")); + } + +} diff --git a/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/warp/MessageHeaderWarpTest.java b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/warp/MessageHeaderWarpTest.java new file mode 100644 index 000000000..d0915b651 --- /dev/null +++ b/arex-instrumentation/mq/arex-integration-mqtt/src/test/java/io/arex/inst/mqtt/inst/warp/MessageHeaderWarpTest.java @@ -0,0 +1,54 @@ +package io.arex.inst.mqtt.inst.warp; + +import io.arex.inst.mqtt.warp.MessageHeaderWarp; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.messaging.MessageHeaders; + +import java.util.HashMap; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +/** + * @author : MentosL + * @date : 2023/5/21 15:39 + */ +public class MessageHeaderWarpTest { + + MessageHeaders messageHeaders = Mockito.mock(MessageHeaders.class); + + @Test + void MessageHeaderWarp1(){ + assertNotNull(new MessageHeaderWarp(messageHeaders)); + MessageHeaders messageHeadersTemp = new MessageHeaders(new HashMap<>()); + assertTrue(new MessageHeaderWarp(messageHeadersTemp).size() == 2); + } + + @Test + void MessageHeaderWarp2(){ + assertNotNull(new MessageHeaderWarp(new HashMap<>())); + assertTrue(new MessageHeaderWarp(new HashMap<>()).size() == 2); + } + + @Test + void MessageHeaderWarp3(){ + assertNotNull(new MessageHeaderWarp(new HashMap<>(), UUID.randomUUID(),System.currentTimeMillis())); + assertTrue(new MessageHeaderWarp(new HashMap<>(), UUID.randomUUID(),System.currentTimeMillis()).size() == 2); + } + + @Test + void put(){ + assertNotNull(new MessageHeaderWarp(new HashMap<>()).put("mock-key","mock-value")); + } + + @Test + void remove(){ + assertDoesNotThrow(() -> new MessageHeaderWarp(new HashMap<>()).remove("mock-key")); + MessageHeaderWarp messageHeaderWarp = new MessageHeaderWarp(new HashMap<>(), UUID.randomUUID(), System.currentTimeMillis()); + assertSame(2,messageHeaderWarp.size()); + messageHeaderWarp.remove("id"); + assertSame(1,messageHeaderWarp.size()); + } + + +} diff --git a/arex-instrumentation/pom.xml b/arex-instrumentation/pom.xml index f1f935c09..cd1f52aee 100644 --- a/arex-instrumentation/pom.xml +++ b/arex-instrumentation/pom.xml @@ -43,6 +43,7 @@ authentication/arex-shiro authentication/arex-jcasbin foundation/arex-serializer + mq/arex-integration-mqtt diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/HttpMessageConvertFactory.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/HttpMessageConvertFactory.java new file mode 100644 index 000000000..b305d27e6 --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/HttpMessageConvertFactory.java @@ -0,0 +1,41 @@ +package io.arex.inst.httpservlet.convert; + +import io.arex.agent.bootstrap.util.CollectionUtil; +import io.arex.inst.httpservlet.adapter.ServletAdapter; +import io.arex.inst.httpservlet.convert.impl.DefaultHttpMessageConverter; + +import java.util.*; + +public class HttpMessageConvertFactory { + + + private static final List cacheList = new ArrayList<>(); + + + + static { + ServiceLoader load = ServiceLoader.load(HttpMessageConverter.class); + if (load != null) { + Iterator iterator = load.iterator(); + while (iterator.hasNext()) { + cacheList.add(iterator.next()); + } + } + } + + + public static HttpMessageConverter getSupportedConverter(P p, R r, ServletAdapter adapter) { + synchronized (HttpMessageConvertFactory.class) { + if (CollectionUtil.isNotEmpty(cacheList)) { + for (HttpMessageConverter converter : cacheList) { + if (converter.match(p, r, adapter)) { + return converter; + } + } + } + return DefaultHttpMessageConverter.getInstance(); + } + } + + +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/HttpMessageConverter.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/HttpMessageConverter.java new file mode 100644 index 000000000..52f2f94ea --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/HttpMessageConverter.java @@ -0,0 +1,10 @@ +package io.arex.inst.httpservlet.convert; + +import io.arex.inst.httpservlet.adapter.ServletAdapter; + +public interface HttpMessageConverter { + + boolean match(HttpServletRequest request,HttpServletResponse response, ServletAdapter adapter); + byte[] getRequest(HttpServletRequest request, ServletAdapter adapter); + byte[] getResponse(HttpServletResponse request, ServletAdapter adapter); +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/ApplicationJsonBodyConverter.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/ApplicationJsonBodyConverter.java new file mode 100644 index 000000000..4e5d760b8 --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/ApplicationJsonBodyConverter.java @@ -0,0 +1,36 @@ +package io.arex.inst.httpservlet.convert.impl; + +import io.arex.agent.bootstrap.util.StringUtil; +import io.arex.inst.httpservlet.adapter.ServletAdapter; +import io.arex.inst.httpservlet.convert.HttpMessageConverter; + +public class ApplicationJsonBodyConverter implements HttpMessageConverter { + + private static final String CONTENT_TYPE = "application/json"; + + @Override + public boolean match(HttpServletRequest request, HttpServletResponse response, ServletAdapter adapter) { + if (request == null || response ==null || adapter == null) { + return false; + } + String contentType = adapter.getContentType(request); + return StringUtil.isNotEmpty(contentType) && contentType.contains(CONTENT_TYPE); + } + + @Override + public byte[] getRequest(HttpServletRequest request, ServletAdapter adapter) { + if (request == null || adapter == null) { + return new byte[0]; + } + return adapter.getRequestBytes(request); + } + + @Override + public byte[] getResponse(HttpServletResponse response, ServletAdapter adapter) { + return new byte[0]; + } +} + + + + diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/ApplicationXmlBodyConverter.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/ApplicationXmlBodyConverter.java new file mode 100644 index 000000000..aa84bd6d4 --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/ApplicationXmlBodyConverter.java @@ -0,0 +1,31 @@ +package io.arex.inst.httpservlet.convert.impl; + +import io.arex.agent.bootstrap.util.StringUtil; +import io.arex.inst.httpservlet.adapter.ServletAdapter; +import io.arex.inst.httpservlet.convert.HttpMessageConverter; + + +public class ApplicationXmlBodyConverter implements HttpMessageConverter { + + private static final String CONTENT_TYPE = "application/xml"; + + @Override + public boolean match(HttpServletRequest request, HttpServletResponse response, ServletAdapter adapter) { + if (request == null || response ==null || adapter == null) { + return false; + } + String contentType = adapter.getContentType(request); + return StringUtil.isNotEmpty(contentType) && contentType.contains(CONTENT_TYPE); + } + + @Override + public byte[] getRequest(HttpServletRequest request, ServletAdapter adapter) { + return new byte[0]; + } + + @Override + public byte[] getResponse(HttpServletResponse response, ServletAdapter adapter) { + return new byte[0]; + } + +} \ No newline at end of file diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/DefaultHttpMessageConverter.java b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/DefaultHttpMessageConverter.java new file mode 100644 index 000000000..33ddf83ee --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/main/java/io/arex/inst/httpservlet/convert/impl/DefaultHttpMessageConverter.java @@ -0,0 +1,35 @@ +package io.arex.inst.httpservlet.convert.impl; + +import io.arex.inst.httpservlet.adapter.ServletAdapter; +import io.arex.inst.httpservlet.convert.HttpMessageConverter; + +public class DefaultHttpMessageConverter implements HttpMessageConverter { + + private DefaultHttpMessageConverter() { + + } + + private static class SingletonHolder { + private static final DefaultHttpMessageConverter INSTANCE = new DefaultHttpMessageConverter(); + } + + public static DefaultHttpMessageConverter getInstance() { + return SingletonHolder.INSTANCE; + } + + + @Override + public boolean match(HttpServletRequest request, HttpServletResponse response, ServletAdapter adapter) { + return false; + } + + @Override + public byte[] getRequest(HttpServletRequest request, ServletAdapter adapter) { + return new byte[0]; + } + + @Override + public byte[] getResponse(HttpServletResponse response, ServletAdapter adapter) { + return new byte[0]; + } +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/HttpMessageConvertFactoryTest.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/HttpMessageConvertFactoryTest.java new file mode 100644 index 000000000..6778462f2 --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/HttpMessageConvertFactoryTest.java @@ -0,0 +1,51 @@ +package io.arex.inst.httpservlet.convert; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +import io.arex.inst.httpservlet.adapter.impl.ServletAdapterImplV3; +import io.arex.inst.httpservlet.adapter.impl.ServletAdapterImplV5; +import io.arex.inst.httpservlet.convert.impl.DefaultHttpMessageConverter; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class HttpMessageConvertFactoryTest { + + HttpServletRequest mockRequest = Mockito.mock(HttpServletRequest.class); + HttpServletResponse mockResponse = Mockito.mock(HttpServletResponse.class); + ServletAdapterImplV3 instance3 = ServletAdapterImplV3.getInstance(); + ServletAdapterImplV5 instance5 = ServletAdapterImplV5.getInstance(); + jakarta.servlet.http.HttpServletRequest mockRequest5 = Mockito.mock(jakarta.servlet.http.HttpServletRequest.class); + jakarta.servlet.http.HttpServletResponse mockResponse5 = Mockito.mock(jakarta.servlet.http.HttpServletResponse.class); + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void getOne(){ + when(mockRequest.getContentType()).thenReturn("application/json"); + when(mockRequest5.getContentType()).thenReturn("application/json"); + assertNotNull(HttpMessageConvertFactory.getSupportedConverter(mockRequest,mockResponse,instance3)); + assertNotNull(HttpMessageConvertFactory.getSupportedConverter(mockRequest5,mockResponse5,instance5)); + } + + @Test + void getDefault(){ + when(mockRequest.getContentType()).thenReturn("application/jn"); + when(mockRequest5.getContentType()).thenReturn("application/jn"); + + assertTrue(HttpMessageConvertFactory.getSupportedConverter(mockRequest,mockResponse,instance3) instanceof DefaultHttpMessageConverter); + assertTrue(HttpMessageConvertFactory.getSupportedConverter(mockRequest5,mockResponse5,instance5) instanceof DefaultHttpMessageConverter); + } + +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/impl/ApplicationJsonBodyConvertTest.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/impl/ApplicationJsonBodyConvertTest.java new file mode 100644 index 000000000..38830c02f --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/impl/ApplicationJsonBodyConvertTest.java @@ -0,0 +1,87 @@ +package io.arex.inst.httpservlet.convert.impl; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + + +import io.arex.inst.httpservlet.adapter.impl.ServletAdapterImplV3; +import io.arex.inst.httpservlet.adapter.impl.ServletAdapterImplV5; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class ApplicationJsonBodyConvertTest { + + HttpServletRequest mockRequest = Mockito.mock(HttpServletRequest.class); + HttpServletResponse mockResponse = Mockito.mock(HttpServletResponse.class); + + ServletAdapterImplV3 instance3 = ServletAdapterImplV3.getInstance(); + ServletAdapterImplV5 instance5 = ServletAdapterImplV5.getInstance(); + jakarta.servlet.http.HttpServletRequest mockRequest5 = Mockito.mock(jakarta.servlet.http.HttpServletRequest.class); + jakarta.servlet.http.HttpServletResponse mockResponse5 = Mockito.mock(jakarta.servlet.http.HttpServletResponse.class); + + + static ApplicationJsonBodyConverter applicationJsonBodyConvert = null; + + + + @BeforeEach + void setUp() { + applicationJsonBodyConvert = new ApplicationJsonBodyConverter(); + + } + + @AfterEach + void tearDown() { + } + + @Test + void match() { + when(mockRequest.getContentType()).thenReturn("application/json"); + when(mockRequest5.getContentType()).thenReturn("application/json"); + assertEquals(true, applicationJsonBodyConvert.match(mockRequest,mockResponse,instance3)); + assertEquals(true, applicationJsonBodyConvert.match(mockRequest5,mockResponse5,instance5)); + } + + + @Test + void notMatch1() { + when(mockRequest.getContentType()).thenReturn("application/jn"); + when(mockRequest5.getContentType()).thenReturn("application/n"); + assertEquals(false, applicationJsonBodyConvert.match(mockRequest,mockResponse,instance3)); + assertEquals(false, applicationJsonBodyConvert.match(mockRequest5,mockResponse5,instance5)); + } + + + @Test + void notMatch2() { + assertEquals(false, applicationJsonBodyConvert.match(null,mockResponse,instance3)); + assertEquals(false, applicationJsonBodyConvert.match(null,mockResponse5,instance5)); + } + + + @Test + void get0Request() { + assertEquals(0, applicationJsonBodyConvert.getRequest(null,instance3).length); + assertEquals(0, applicationJsonBodyConvert.getRequest(null,instance5).length); + } + + @Test + void getRequest() { + assertEquals(0, applicationJsonBodyConvert.getRequest(instance3.wrapRequest(mockRequest),instance3).length); + assertEquals(0, applicationJsonBodyConvert.getRequest(instance5.wrapRequest(mockRequest5),instance5).length); + } + + + @Test + void getResponse() { + assertEquals(0, applicationJsonBodyConvert.getResponse(instance3.wrapResponse(mockResponse),instance3).length); + assertEquals(0, applicationJsonBodyConvert.getResponse(instance5.wrapResponse(mockResponse5),instance5).length); + } + +} diff --git a/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/impl/ApplicationXmlBodyConvertTest.java b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/impl/ApplicationXmlBodyConvertTest.java new file mode 100644 index 000000000..09cb0276f --- /dev/null +++ b/arex-instrumentation/servlet/arex-httpservlet/src/test/java/io/arex/inst/httpservlet/convert/impl/ApplicationXmlBodyConvertTest.java @@ -0,0 +1,86 @@ +package io.arex.inst.httpservlet.convert.impl; + +import io.arex.inst.httpservlet.adapter.impl.ServletAdapterImplV3; +import io.arex.inst.httpservlet.adapter.impl.ServletAdapterImplV5; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +public class ApplicationXmlBodyConvertTest { + + HttpServletRequest mockRequest = Mockito.mock(HttpServletRequest.class); + HttpServletResponse mockResponse = Mockito.mock(HttpServletResponse.class); + + ServletAdapterImplV3 instance3 = ServletAdapterImplV3.getInstance(); + ServletAdapterImplV5 instance5 = ServletAdapterImplV5.getInstance(); + jakarta.servlet.http.HttpServletRequest mockRequest5 = Mockito.mock(jakarta.servlet.http.HttpServletRequest.class); + jakarta.servlet.http.HttpServletResponse mockResponse5 = Mockito.mock(jakarta.servlet.http.HttpServletResponse.class); + + + static ApplicationXmlBodyConverter applicationXmlBodyConvert = null; + + + + @BeforeEach + void setUp() { + applicationXmlBodyConvert = new ApplicationXmlBodyConverter(); + + } + + @AfterEach + void tearDown() { + } + + @Test + void match() { + when(mockRequest.getContentType()).thenReturn("application/xml"); + when(mockRequest5.getContentType()).thenReturn("application/xml"); + assertEquals(true, applicationXmlBodyConvert.match(mockRequest,mockResponse,instance3)); + assertEquals(true, applicationXmlBodyConvert.match(mockRequest5,mockResponse5,instance5)); + } + + + @Test + void notMatch1() { + when(mockRequest.getContentType()).thenReturn("application/xl"); + when(mockRequest5.getContentType()).thenReturn("application/xl"); + assertEquals(false, applicationXmlBodyConvert.match(mockRequest,mockResponse,instance3)); + assertEquals(false, applicationXmlBodyConvert.match(mockRequest5,mockResponse5,instance5)); + } + + + @Test + void notMatch2() { + assertEquals(false, applicationXmlBodyConvert.match(null,mockResponse,instance3)); + assertEquals(false, applicationXmlBodyConvert.match(null,mockResponse5,instance5)); + } + + + @Test + void get0Request() { + assertEquals(0, applicationXmlBodyConvert.getRequest(null,instance3).length); + assertEquals(0, applicationXmlBodyConvert.getRequest(null,instance5).length); + } + + @Test + void getRequest() { + assertEquals(0, applicationXmlBodyConvert.getRequest(instance3.wrapRequest(mockRequest),instance3).length); + assertEquals(0, applicationXmlBodyConvert.getRequest(instance5.wrapRequest(mockRequest5),instance5).length); + } + + + @Test + void getResponse() { + assertEquals(0, applicationXmlBodyConvert.getResponse(instance3.wrapResponse(mockResponse),instance3).length); + assertEquals(0, applicationXmlBodyConvert.getResponse(instance5.wrapResponse(mockResponse5),instance5).length); + } + +}