This is an automated email from the ASF dual-hosted git repository.

timothyjward pushed a commit to branch backport/records
in repository https://gitbox.apache.org/repos/asf/aries-typedevent.git

commit f56ca7ccd24cf8760a1c64a18e6ec5fffa63bd84
Author: Tim Ward <[email protected]>
AuthorDate: Wed Mar 6 15:58:40 2024 +0000

    Backport support for Records as events from 1.1 dev branch
    
    This commit makes the Aries Typed Event bundle a multi-release bundle which 
works on Java 8, but adds support for Record types on Java 16 or later. Event 
Publishers and Event Handlers can use Record types as normal.
    
    The build now requires Java 17 (the LTS release) as a result of this change.
    
    Signed-off-by: Tim Ward <[email protected]>
    (cherry picked from commit 7b6220ac1c4b5d1736477789cdd259444896a39c)
---
 org.apache.aries.typedevent.bus/bnd.bnd            |   1 +
 org.apache.aries.typedevent.bus/pom.xml            |  27 ++
 .../aries/typedevent/bus/impl/EventConverter.java  |  10 +-
 .../aries/typedevent/bus/impl/RecordConverter.java |  36 +++
 .../typedevent/bus/impl/TypedEventBusImpl.java     |   2 +-
 .../aries/typedevent/bus/impl/RecordConverter.java | 102 ++++++++
 .../typedevent/bus/osgi/RecordIntegrationTest.java | 272 +++++++++++++++++++++
 pom.xml                                            |   2 +-
 8 files changed, 448 insertions(+), 4 deletions(-)

diff --git a/org.apache.aries.typedevent.bus/bnd.bnd 
b/org.apache.aries.typedevent.bus/bnd.bnd
new file mode 100644
index 0000000..6cd22a6
--- /dev/null
+++ b/org.apache.aries.typedevent.bus/bnd.bnd
@@ -0,0 +1 @@
+Multi-Release: true 
\ No newline at end of file
diff --git a/org.apache.aries.typedevent.bus/pom.xml 
b/org.apache.aries.typedevent.bus/pom.xml
index 0bba981..8a62413 100644
--- a/org.apache.aries.typedevent.bus/pom.xml
+++ b/org.apache.aries.typedevent.bus/pom.xml
@@ -109,6 +109,33 @@
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-run-maven-plugin</artifactId>
             </plugin>
+            <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-compiler-plugin</artifactId>
+                               <executions>
+                                       <execution>
+                                               <id>java16-compile</id>
+                                               <goals>
+                                                       <goal>compile</goal>
+                                               </goals>
+                                               <configuration>
+                                                       
<compileSourceRoots>${project.basedir}/src/main/java16</compileSourceRoots>
+                                                       <source>16</source>
+                                                       <target>16</target>
+                                                       <release>16</release>
+                                                       
<multiReleaseOutput>true</multiReleaseOutput>
+                                               </configuration>
+                                       </execution>
+                                       <execution>
+                                               <id>default-testCompile</id>
+                                               <configuration>
+                                                       <source>16</source>
+                                                       <target>16</target>
+                                                       <release>16</release>
+                                               </configuration>
+                                       </execution>
+                               </executions>
+                       </plugin>
         </plugins>
     </build>
 </project>
\ No newline at end of file
diff --git 
a/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/EventConverter.java
 
b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/EventConverter.java
index d2f8851..3fc5588 100644
--- 
a/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/EventConverter.java
+++ 
b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/EventConverter.java
@@ -66,7 +66,7 @@ public class EventConverter {
     };
     private static final TypeReference<Set<Object>> SET_OF_OBJECTS = new 
TypeReference<Set<Object>>() {
     };
-    private static final TypeReference<Map<String, Object>> 
MAP_WITH_STRING_KEYS = new TypeReference<Map<String, Object>>() {
+    static final TypeReference<Map<String, Object>> MAP_WITH_STRING_KEYS = new 
TypeReference<Map<String, Object>>() {
     };
     private static final TypeReference<Map<Object, Object>> 
MAP_OF_OBJECT_TO_OBJECT = new TypeReference<Map<Object, Object>>() {
     };
@@ -105,10 +105,16 @@ public class EventConverter {
         specialClasses.add(ZonedDateTime.class);
         specialClasses.add(UUID.class);
 
-        eventConverter = 
Converters.standardConverter().newConverterBuilder().rule(EventConverter::convert)
+        eventConverter = Converters.standardConverter().newConverterBuilder()
+                       .rule(EventConverter::convertRecord)
+                       .rule(EventConverter::convert)
                 .errorHandler(EventConverter::attemptRecovery).build();
     }
 
+    static Object convertRecord(Object o, Type target) {
+       return RecordConverter.convert(eventConverter, o, target);
+    }
+    
     /**
      * Conversion for nested Map values
      * @param o - the value to convert
diff --git 
a/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/RecordConverter.java
 
b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/RecordConverter.java
new file mode 100644
index 0000000..ef0a516
--- /dev/null
+++ 
b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/RecordConverter.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.aries.typedevent.bus.impl;
+
+import java.lang.reflect.Type;
+
+import org.osgi.util.converter.Converter;
+import org.osgi.util.converter.ConverterFunction;
+
+/**
+ * This class is responsible for converting Record events to and from their 
+ * "flattened" representations. As Java 8 doesn't support Records this is
+ * not handled
+ */
+public class RecordConverter {
+
+    static Object convert(Converter converter, Object o, Type target) {
+       return ConverterFunction.CANNOT_HANDLE;
+    }
+
+}
\ No newline at end of file
diff --git 
a/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java
 
b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java
index 7398224..85ea79c 100644
--- 
a/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java
+++ 
b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java
@@ -178,7 +178,7 @@ public class TypedEventBusImpl implements TypedEventBus, 
AriesTypedEvents {
             }
         } else {
             Class<?> toCheck = handler.getClass();
-            outer: while(genType == null) {
+            outer: while(genType == null && toCheck != null) {
                 genType = findDirectlyImplemented(toCheck);
                 
                 if(genType != null) {
diff --git 
a/org.apache.aries.typedevent.bus/src/main/java16/org/apache/aries/typedevent/bus/impl/RecordConverter.java
 
b/org.apache.aries.typedevent.bus/src/main/java16/org/apache/aries/typedevent/bus/impl/RecordConverter.java
new file mode 100644
index 0000000..1e32c3c
--- /dev/null
+++ 
b/org.apache.aries.typedevent.bus/src/main/java16/org/apache/aries/typedevent/bus/impl/RecordConverter.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.aries.typedevent.bus.impl;
+
+import static java.util.stream.Collectors.toMap;
+
+import java.lang.reflect.RecordComponent;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Map;
+
+import org.osgi.util.converter.ConversionException;
+import org.osgi.util.converter.Converter;
+import org.osgi.util.converter.ConverterFunction;
+
+/**
+ * This class is responsible for converting Record events to and from their 
+ * "flattened" representations. This version runs on Java 17
+ */
+public class RecordConverter {
+
+    static Object convert(Converter converter, Object o, Type target) {
+
+        if (Record.class.isInstance(o)) {
+               RecordComponent[] sourceComponents = 
o.getClass().getRecordComponents();
+               
+               if(target instanceof Class<?> clz && 
Record.class.isAssignableFrom(clz)) {
+                       RecordComponent[] targetComponents = 
clz.getRecordComponents();
+                       Object[] args = new Object[targetComponents.length];
+                       Class<?>[] argTypes = new 
Class<?>[targetComponents.length];
+                       for(int i = 0; i < targetComponents.length; i++) {
+                               RecordComponent targetComponent = 
targetComponents[i];
+                                       String name = targetComponent.getName();
+                               Object arg = null;
+                               for(int j = 0; j < sourceComponents.length; 
j++) {
+                                       
if(sourceComponents[j].getName().equals(name)) {
+                                               Object sourceArg = 
getComponentValue(sourceComponents[j], o);
+                                                       Type targetArgType = 
targetComponent.getGenericType();
+                                                       arg = 
converter.convert(sourceArg).to(targetArgType); 
+                                               break;          
+                                       }
+                               }
+                               args[i] = arg;
+                                       argTypes[i] = targetComponent.getType();
+                       }
+                       return createRecord(clz, args, argTypes);
+               } else {
+                       Map<String, Object> converted = 
Arrays.stream(sourceComponents)
+                                       
.collect(toMap(RecordComponent::getName, rc -> getComponentValue(rc, o)));
+                       
+                       return converter.convert(converted).to(target);
+               }
+        } else if(target instanceof Class<?> clz && 
Record.class.isAssignableFrom(clz)) {
+               Map<String, Object> intermediate = 
converter.convert(o).to(EventConverter.MAP_WITH_STRING_KEYS);
+               RecordComponent[] targetComponents = clz.getRecordComponents();
+               Object[] args = new Object[targetComponents.length];
+               Class<?>[] argTypes = new Class<?>[targetComponents.length];
+               for(int i = 0; i < targetComponents.length; i++) {
+                       RecordComponent targetComponent = targetComponents[i];
+                               Object sourceArg = 
intermediate.get(targetComponent.getName());
+                               Type targetArgType = 
targetComponent.getGenericType();
+                       args[i] = 
converter.convert(sourceArg).to(targetArgType); 
+                               argTypes[i] = targetComponent.getType();
+               }
+               return createRecord(clz, args, argTypes);
+        }
+
+        return ConverterFunction.CANNOT_HANDLE;
+        
+    }
+
+       private static Object createRecord(Class<?> clz, Object[] args, 
Class<?>[] argTypes) {
+               try {
+                       return 
clz.getDeclaredConstructor(argTypes).newInstance(args);
+               } catch (Exception e) {
+                       throw new ConversionException("Unable to instantiate 
record component " + clz.getName(), e);
+               }
+       }
+    
+    private static Object getComponentValue(RecordComponent rc, Object o) {
+       try {
+               return rc.getAccessor().invoke(o);
+       } catch (Exception e) {
+               throw new ConversionException("Unable to process record 
component " + rc.getName() + " from type " + rc.getDeclaringRecord().getName(), 
e);
+       }
+    }
+}
\ No newline at end of file
diff --git 
a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/RecordIntegrationTest.java
 
b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/RecordIntegrationTest.java
new file mode 100644
index 0000000..8b5d69d
--- /dev/null
+++ 
b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/RecordIntegrationTest.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or eventBusied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aries.typedevent.bus.osgi;
+
+import static 
org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_FILTER;
+import static 
org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_TOPICS;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.aries.typedevent.bus.common.TestEvent;
+import org.apache.aries.typedevent.bus.common.TestEventConsumer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.typedevent.TypedEventBus;
+import org.osgi.service.typedevent.TypedEventHandler;
+import org.osgi.test.common.annotation.InjectBundleContext;
+import org.osgi.test.common.annotation.InjectService;
+import org.osgi.test.junit5.context.BundleContextExtension;
+import org.osgi.test.junit5.service.ServiceExtension;
+
+/**
+ * This is a JUnit test that will be run inside an OSGi framework.
+ * 
+ * It can interact with the framework by starting or stopping bundles,
+ * getting or registering services, or in other ways, and then observing
+ * the result on the bundle(s) being tested.
+ */
+@ExtendWith(BundleContextExtension.class)
+@ExtendWith(ServiceExtension.class)
+@ExtendWith(MockitoExtension.class)
+public class RecordIntegrationTest extends AbstractIntegrationTest {
+    
+       private static final String TOPIC = "org/apache/aries/test/record";
+       
+    @InjectBundleContext
+    BundleContext context;
+    
+    @InjectService
+    TypedEventBus eventBus;
+    
+    @Mock
+    TestEventConsumer typedEventHandler;
+
+    @Mock
+    TestRecordListener recordEventHandler;
+
+    @Test
+    public void testUnFilteredListenerEventToRecord() throws Exception {
+       Dictionary<String, Object> props = new Hashtable<>();
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
typedEventHandler, props));
+       
+       props = new Hashtable<>();
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
recordEventHandler, props));
+       
+       // Event to record
+       
+       TestEvent event = new TestEvent();
+       event.message = "foo";
+       
+       eventBus.deliver(TOPIC, event);
+       
+       Mockito.verify(typedEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("foo")));
+       
+       Mockito.verify(recordEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("foo")));
+    }
+
+    @Test
+    public void testUnFilteredListenerRecordToEvent() throws Exception {
+       Dictionary<String, Object> props = new Hashtable<>();
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
typedEventHandler, props));
+       
+       props = new Hashtable<>();
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
recordEventHandler, props));
+       
+       // Record to Event
+       TestRecord testRecord = new TestRecord("bar");
+       
+       eventBus.deliver(TOPIC, testRecord);
+       
+       Mockito.verify(typedEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("bar")));
+       
+       Mockito.verify(recordEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("bar")));
+       
+    }
+
+    @Test
+    public void testUnFilteredListenerRecordToRecord() throws Exception {
+       Dictionary<String, Object> props = new Hashtable<>();
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
typedEventHandler, props));
+       
+       props = new Hashtable<>();
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
recordEventHandler, props));
+       
+       // Record to Record
+       TestRecord2 testRecord2 = new TestRecord2("foobar", 5);
+       
+       eventBus.deliver(TOPIC, testRecord2);
+       
+       Mockito.verify(typedEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("foobar")));
+       
+       Mockito.verify(recordEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("foobar")));
+       
+    }
+
+    @Test
+    public void testFilteredListenerEventToRecord() throws Exception {
+        Dictionary<String, Object> props = new Hashtable<>();
+        props.put(TYPED_EVENT_FILTER, "(message=foo)");
+        props.put(TYPED_EVENT_TOPICS, TOPIC);
+        
+        regs.add(context.registerService(TypedEventHandler.class, 
typedEventHandler, props));
+        
+        props = new Hashtable<>();
+        props.put(TYPED_EVENT_FILTER, "(message=bar)");
+        props.put(TYPED_EVENT_TOPICS, TOPIC);
+        
+        regs.add(context.registerService(TypedEventHandler.class, 
recordEventHandler, props));
+        
+        // Event to record
+        
+        TestEvent event = new TestEvent();
+        event.message = "foo";
+        
+        eventBus.deliver(TOPIC, event);
+        
+        Mockito.verify(typedEventHandler, Mockito.timeout(1000))
+            .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("foo")));
+
+        Mockito.verify(recordEventHandler, Mockito.after(1000).never())
+            .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("foo")));
+        
+        
+        event = new TestEvent();
+        event.message = "bar";
+        
+        eventBus.deliver(TOPIC, event);
+        
+        Mockito.verify(recordEventHandler, Mockito.timeout(1000))
+            .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("bar")));
+
+        Mockito.verify(typedEventHandler, Mockito.after(1000).never())
+            .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("bar")));
+    }
+
+    @Test
+    public void testFilteredListenerRecordToEvent() throws Exception {
+       Dictionary<String, Object> props = new Hashtable<>();
+       props.put(TYPED_EVENT_FILTER, "(message=foo)");
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
typedEventHandler, props));
+       
+       props = new Hashtable<>();
+       props.put(TYPED_EVENT_FILTER, "(message=bar)");
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
recordEventHandler, props));
+       
+       // Record to Event
+       TestRecord testRecord = new TestRecord("foo");
+       
+       eventBus.deliver(TOPIC, testRecord);
+       
+       Mockito.verify(typedEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("foo")));
+       
+       Mockito.verify(recordEventHandler, Mockito.after(1000).never())
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("foo")));
+       
+       
+       testRecord = new TestRecord("bar");
+       
+       eventBus.deliver(TOPIC, testRecord);
+       
+       Mockito.verify(recordEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("bar")));
+       
+       Mockito.verify(typedEventHandler, Mockito.after(1000).never())
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("bar")));
+    }
+
+    @Test
+    public void testFilteredListenerRecordToRecord() throws Exception {
+       Dictionary<String, Object> props = new Hashtable<>();
+       props.put(TYPED_EVENT_FILTER, "(message=foo)");
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
typedEventHandler, props));
+       
+       props = new Hashtable<>();
+       props.put(TYPED_EVENT_FILTER, "(message=bar)");
+       props.put(TYPED_EVENT_TOPICS, TOPIC);
+       
+       regs.add(context.registerService(TypedEventHandler.class, 
recordEventHandler, props));
+       
+       // Record to Record
+       TestRecord2 testRecord2 = new TestRecord2("foo", 5);
+       
+       eventBus.deliver(TOPIC, testRecord2);
+       
+       Mockito.verify(typedEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("foo")));
+       
+       Mockito.verify(recordEventHandler, Mockito.after(1000).never())
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("foo")));
+       
+       
+       testRecord2 = new TestRecord2("bar", 5);
+       
+       eventBus.deliver(TOPIC, testRecord2);
+       
+       Mockito.verify(recordEventHandler, Mockito.timeout(1000))
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestRecordWithMessage("bar")));
+       
+       Mockito.verify(typedEventHandler, Mockito.after(1000).never())
+       .notify(Mockito.eq(TOPIC), 
Mockito.argThat(isTestEventWithMessage("bar")));
+    }
+    
+    public interface TestRecordListener extends TypedEventHandler<TestRecord> 
{}
+    
+    public record TestRecord(String message) {}
+    
+    public record TestRecord2(String message, int count) {}
+    
+    protected ArgumentMatcher<TestRecord> isTestRecordWithMessage(String 
message) {
+        return new ArgumentMatcher<TestRecord>() {
+            
+            @Override
+            public boolean matches(TestRecord argument) {
+                return message.equals(argument.message);
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 52d4c2c..f85a904 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,7 +58,7 @@
     </repositories>
 
     <properties>
-        <bnd.version>6.4.0</bnd.version>
+        <bnd.version>7.0.0</bnd.version>
         <dsl.version>1.2.2</dsl.version>
         <junit.version>5.10.0</junit.version>
         <mockito.version>5.5.0</mockito.version>

Reply via email to