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

vavrtom pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git


The following commit(s) were added to refs/heads/main by this push:
     new e273a0bdb8 QPID-8601: [Broker-J] Broker-J instrumentation agent (#141)
e273a0bdb8 is described below

commit e273a0bdb8daef63e7872355641658efdff619f0
Author: Daniil Kirilyuk <[email protected]>
AuthorDate: Tue Oct 11 15:56:08 2022 +0200

    QPID-8601: [Broker-J] Broker-J instrumentation agent (#141)
    
    * QPID-8601: [Broker-J] Broker-J instrumentation agent
    
    * QPID-8601: [Broker-J] Added new line to end of file
    
    Co-authored-by: vavrtom <[email protected]>
---
 .../server/model/AbstractConfiguredObject.java     |  17 +-
 .../server/model/ConfiguredObjectTypeRegistry.java |  19 +-
 broker-instrumentation/README.md                   |  30 ++
 broker-instrumentation/pom.xml                     |  68 +++
 .../server/instrumentation/agent/QpidAgent.java    |  39 ++
 .../metadata/AutomatedFieldDescription.java        | 122 +++++
 .../metadata/MemberDescription.java                |  32 ++
 .../metadata/MethodDescription.java                | 177 +++++++
 .../transformer/AbstractQpidTransformer.java       | 572 +++++++++++++++++++++
 ...bjectMethodAttributeOrStatisticTransformer.java | 279 ++++++++++
 ...ConfiguredObjectMethodOperationTransformer.java | 366 +++++++++++++
 .../ConfiguredObjectTypeRegistryTransformer.java   | 427 +++++++++++++++
 .../transformer/QpidClassFileTransformer.java      | 344 +++++++++++++
 .../transformer/QpidTransformer.java               |  65 +++
 .../plugins/AbstractConnectionLimitProvider.java   |   2 +-
 .../AbstractFileBasedConnectionLimitProvider.java  |   2 +-
 .../AbstractRuleBasedConnectionLimitProvider.java  |   2 +-
 doc/java-broker/src/docbkx/Java-Broker-Runtime.xml |   1 +
 .../Java-Broker-Runtime-Instrumentation.xml        |  31 ++
 pom.xml                                            |  12 +
 20 files changed, 2583 insertions(+), 24 deletions(-)

diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java
 
b/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java
index d8144f8ecd..0e8a7ee756 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/model/AbstractConfiguredObject.java
@@ -486,24 +486,13 @@ public abstract class AbstractConfiguredObject<X extends 
ConfiguredObject<X>> im
         try
         {
             final ConfiguredAutomatedAttribute attribute = 
(ConfiguredAutomatedAttribute) _attributeTypes.get(name);
-            if(value == null && !"".equals(attribute.defaultValue()))
+            if (value == null && !"".equals(attribute.defaultValue()))
             {
                 value = attribute.defaultValue();
             }
             ConfiguredObjectTypeRegistry.AutomatedField field = 
_automatedFields.get(name);
-
-            if(field.getPreSettingAction() != null)
-            {
-                field.getPreSettingAction().invoke(this);
-            }
-
             Object desiredValue = attribute.convert(value, this);
-            field.getField().set(this, desiredValue);
-
-            if(field.getPostSettingAction() != null)
-            {
-                field.getPostSettingAction().invoke(this);
-            }
+            field.set(this, desiredValue);
         }
         catch (IllegalAccessException e)
         {
@@ -511,7 +500,7 @@ public abstract class AbstractConfiguredObject<X extends 
ConfiguredObject<X>> im
         }
         catch (InvocationTargetException e)
         {
-            if(e.getCause() instanceof RuntimeException)
+            if (e.getCause() instanceof RuntimeException)
             {
                 throw (RuntimeException) e.getCause();
             }
diff --git 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectTypeRegistry.java
 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectTypeRegistry.java
index 79b5fc6167..59d4193f1f 100644
--- 
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectTypeRegistry.java
+++ 
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectTypeRegistry.java
@@ -46,6 +46,7 @@ import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -705,18 +706,22 @@ public class ConfiguredObjectTypeRegistry
             return _field;
         }
 
-        public Method getPreSettingAction()
+        public void set(ConfiguredObject configuredObject, Object 
desiredValue) throws IllegalAccessException, InvocationTargetException
         {
-            return _preSettingAction;
-        }
+            if (_preSettingAction != null)
+            {
+                _preSettingAction.invoke(configuredObject);
+            }
 
-        public Method getPostSettingAction()
-        {
-            return _postSettingAction;
+            _field.set(configuredObject, desiredValue);
+
+            if (_postSettingAction != null)
+            {
+                _postSettingAction.invoke(configuredObject);
+            }
         }
     }
 
-
     private <X extends ConfiguredObject> void process(final Class<X> clazz)
     {
         synchronized (_allAttributes)
diff --git a/broker-instrumentation/README.md b/broker-instrumentation/README.md
new file mode 100644
index 0000000000..d500f6a7b2
--- /dev/null
+++ b/broker-instrumentation/README.md
@@ -0,0 +1,30 @@
+Module provides a static instrumentation agent, which transforms broker classes
+
+* org.apache.qpid.server.model.ConfiguredObjectMethodAttributeOrStatistic
+* org.apache.qpid.server.model.ConfiguredObjectMethodOperation
+* org.apache.qpid.server.model.ConfiguredObjectTypeRegistry$AutomatedField
+
+replacing reflection calls method.invoke() with static final 
MethodHandle.invokeExact().
+
+To use instrumentation agent following JVM argument should be added to the 
broker start 
+parameters:
+
+```
+-javaagent:$BROKER_DIR/lib/broker-instrumentation-9.0.0-SNAPSHOT.jar
+```
+
+List of classes to instrument can be supplied as a comma separated list:
+
+```
+-javaagent:$BROKER_DIR/lib/broker-instrumentation-9.0.0-SNAPSHOT.jar=ConfiguredObjectMethodAttributeOrStatistic
+```
+
+```
+-javaagent:$BROKER_DIR/lib/broker-instrumentation-9.0.0-SNAPSHOT.jar=ConfiguredObjectMethodAttributeOrStatistic,ConfiguredObjectMethodOperation
+```
+
+```
+-javaagent:$BROKER_DIR/lib/broker-instrumentation-9.0.0-SNAPSHOT.jar=ConfiguredObjectMethodAttributeOrStatistic,ConfiguredObjectMethodOperation,AutomatedField
+```
+
+When no arguments supplied, all classes will be instrumented.
\ No newline at end of file
diff --git a/broker-instrumentation/pom.xml b/broker-instrumentation/pom.xml
new file mode 100644
index 0000000000..a2f4444c90
--- /dev/null
+++ b/broker-instrumentation/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <parent>
+        <artifactId>qpid-broker-parent</artifactId>
+        <groupId>org.apache.qpid</groupId>
+        <version>9.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>broker-instrumentation</artifactId>
+    <name>Apache Qpid Broker-J Instrumentation</name>
+    <description>Broker instrumentation</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.ow2.asm</groupId>
+            <artifactId>asm</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.ow2.asm</groupId>
+            <artifactId>asm-tree</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.qpid</groupId>
+            <artifactId>qpid-broker-core</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <!--version specified in parent pluginManagement -->
+                <configuration>
+                    <archive>
+                        <manifestEntries>
+                            
<Agent-Class>org.apache.qpid.server.instrumentation.agent.QpidAgent</Agent-Class>
+                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
+                            
<Can-Retransform-Classes>true</Can-Retransform-Classes>
+                            
<Premain-Class>org.apache.qpid.server.instrumentation.agent.QpidAgent</Premain-Class>
+                            
<Boot-Class-Path>asm-${asm-version}.jar:asm-tree-${asm-version}.jar</Boot-Class-Path>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/agent/QpidAgent.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/agent/QpidAgent.java
new file mode 100644
index 0000000000..87a384f4a9
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/agent/QpidAgent.java
@@ -0,0 +1,39 @@
+/*
+ * 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.qpid.server.instrumentation.agent;
+
+import java.lang.instrument.Instrumentation;
+
+import 
org.apache.qpid.server.instrumentation.transformer.QpidClassFileTransformer;
+
+/** Uses static instrumentation to modify broker classes before loading */
+@SuppressWarnings("java:S1172")
+public class QpidAgent
+{
+    private QpidAgent()
+    {
+
+    }
+
+    public static void premain(final String args, final Instrumentation 
instrumentation)
+    {
+        instrumentation.addTransformer(new QpidClassFileTransformer(args, 
instrumentation));
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/AutomatedFieldDescription.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/AutomatedFieldDescription.java
new file mode 100644
index 0000000000..e3463513e2
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/AutomatedFieldDescription.java
@@ -0,0 +1,122 @@
+/*
+ * 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.qpid.server.instrumentation.metadata;
+
+import java.util.Objects;
+
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+
+/**
+ * Bean holding automated field metadata
+ */
+// sonar complains about underscores in variable names
+@SuppressWarnings("java:S116")
+public class AutomatedFieldDescription implements MemberDescription
+{
+    private final String _declaringClass;
+    private final String _name;
+    private final MethodDescription _beforeSet;
+    private final MethodDescription _afterSet;
+
+    public AutomatedFieldDescription(final String declaringClass,
+                                     final String name,
+                                     final MethodDescription beforeSet,
+                                     final MethodDescription afterSet)
+    {
+        _declaringClass = declaringClass;
+        _name = name;
+        _beforeSet = beforeSet;
+        _afterSet = afterSet;
+    }
+
+    public static AutomatedFieldDescription of(final ClassNode classNode,
+                                               final FieldNode fieldNode,
+                                               final AnnotationNode 
annotationNode)
+    {
+        final String declaringClass = classNode.name;
+        final String fieldName = fieldNode.name;
+        MethodDescription beforeSet = null;
+        MethodDescription afterSet = null;
+        if (annotationNode.values != null)
+        {
+            for (int i = 0; i < annotationNode.values.size(); i++)
+            {
+                final Object name = annotationNode.values.get(i);
+                if ("beforeSet".equals(name))
+                {
+                    beforeSet = createMethodDescription(classNode, 
annotationNode, i);
+                }
+                if ("afterSet".equals(name))
+                {
+                    afterSet = createMethodDescription(classNode, 
annotationNode, i);
+                }
+            }
+        }
+        return new AutomatedFieldDescription(declaringClass, fieldName, 
beforeSet, afterSet);
+    }
+
+    private static MethodDescription createMethodDescription(final ClassNode 
classNode,
+                                                             final 
AnnotationNode annotationNode,
+                                                             final int i)
+    {
+        final String beforeSetName = (String) annotationNode.values.get(i + 1);
+        return classNode.methods.stream()
+                .filter(methodNode -> Objects.equals(beforeSetName, 
methodNode.name))
+                .findFirst().map(node -> MethodDescription.of(classNode, 
node)).orElse(null);
+    }
+
+    public String getDeclaringClass()
+    {
+        return _declaringClass;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public String getSignature()
+    {
+        return _declaringClass.replace('/', '.') + "#" + _name;
+    }
+
+    public MethodDescription getBeforeSet()
+    {
+        return _beforeSet;
+    }
+
+    public MethodDescription getAfterSet()
+    {
+        return _afterSet;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "AutomatedFieldDescription{" +
+               "_declaringClass='" + _declaringClass + '\'' +
+               ", _name='" + _name + '\'' +
+               ", _beforeSet=" + _beforeSet +
+               ", _afterSet=" + _afterSet +
+               '}';
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/MemberDescription.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/MemberDescription.java
new file mode 100644
index 0000000000..e0c23f1037
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/MemberDescription.java
@@ -0,0 +1,32 @@
+/*
+ * 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.qpid.server.instrumentation.metadata;
+
+/**
+ * Class member description
+ */
+public interface MemberDescription
+{
+    String getDeclaringClass();
+
+    String getName();
+
+    String getSignature();
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/MethodDescription.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/MethodDescription.java
new file mode 100644
index 0000000000..d0f51c248a
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/metadata/MethodDescription.java
@@ -0,0 +1,177 @@
+/*
+ * 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.qpid.server.instrumentation.metadata;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.MethodNode;
+
+/**
+ * Bean holding method metadata
+ */
+// sonar complains about underscores in variable names
+@SuppressWarnings("java:S116")
+public class MethodDescription implements MemberDescription
+{
+    /**
+     * Method declaring class, e.g. java/lang/Object
+     */
+    private final String _declaringClass;
+
+    /**
+     * Method name, e.g. equals
+     */
+    private final String _name;
+
+    /**
+     * Method return type, e.g. java/lang/Object or Z / B / I for primitives
+     */
+    private final String _returnType;
+
+    /**
+     * Method parameters
+     */
+    private final List<String> _parameters;
+
+    private static final Map<String, Supplier<String>> TYPES = Map.of(
+            "Z", () -> "boolean",
+            "B", () -> "byte",
+            "C", () -> "char",
+            "D", () -> "double",
+            "F", () -> "float",
+            "I", () -> "int",
+            "J", () -> "long",
+            "S", () -> "short");
+
+    private static final List<String> PRIMITIVES = List.of("B", "Z", "C", "D", 
"F", "I", "J", "S", "V");
+
+    public MethodDescription(final String declaringClass,
+                             final String name,
+                             final String returnType,
+                             final List<String> parameters)
+    {
+        _declaringClass = declaringClass;
+        _name = name;
+        _returnType = returnType;
+        _parameters = new ArrayList<>(parameters);
+    }
+
+    public static MethodDescription of(final ClassNode classNode,
+                                       final MethodNode methodNode)
+    {
+        final String declaringClass = classNode.name;
+        final String methodName = methodNode.name;
+        final String returnType = 
Type.getReturnType(methodNode.desc).getInternalName();
+        final List<String> parameters = 
Arrays.stream(Type.getArgumentTypes(methodNode.desc))
+                .map(Type::getInternalName).collect(Collectors.toList());
+        return new MethodDescription(declaringClass, methodName, returnType, 
parameters);
+    }
+
+    public String getDeclaringClass()
+    {
+        return _declaringClass;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public String getReturnType()
+    {
+        return _returnType;
+    }
+
+    public List<String> getParameters()
+    {
+        return new ArrayList<>(_parameters);
+    }
+
+    public String getParameter(int i)
+    {
+        return _parameters.get(i);
+    }
+
+    public int getParameterCount()
+    {
+        return _parameters.size();
+    }
+
+    public boolean returnsVoid()
+    {
+        return "V".equalsIgnoreCase(_returnType);
+    }
+
+    public boolean returnsPrimitive()
+    {
+        return PRIMITIVES.contains(_returnType);
+    }
+
+    public String getSignature()
+    {
+        return _declaringClass.replace('/', '.') + "#" + _name +
+               (_parameters.isEmpty() ? "" : _parameters.stream()
+                    .map(param -> param.lastIndexOf('/') == -1 ?
+                            param : param.substring(param.lastIndexOf('/') + 
1))
+                    .map(param -> TYPES.getOrDefault(param, () -> param).get())
+                    .collect(Collectors.joining()));
+    }
+
+    @Override
+    public String toString()
+    {
+        return "MethodDescription{" +
+               "declaringClass='" + _declaringClass + '\'' +
+               ", name='" + _name + '\'' +
+               ", returnType='" + _returnType + '\'' +
+               '}';
+    }
+
+    @Override
+    public boolean equals(final Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+        final MethodDescription that = (MethodDescription) o;
+        return Objects.equals(_declaringClass, that._declaringClass) &&
+               Objects.equals(_name, that._name) &&
+               Objects.equals(_parameters, that._parameters);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash(_declaringClass, _name, _parameters);
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/AbstractQpidTransformer.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/AbstractQpidTransformer.java
new file mode 100644
index 0000000000..9e76038a82
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/AbstractQpidTransformer.java
@@ -0,0 +1,572 @@
+/*
+ * 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.qpid.server.instrumentation.transformer;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.LambdaMetafactory;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+import org.apache.qpid.server.instrumentation.metadata.MemberDescription;
+import org.apache.qpid.server.instrumentation.metadata.MethodDescription;
+import org.apache.qpid.server.util.ServerScopedRuntimeException;
+
+/**
+ * Class contains constants and utility methods for instrumentation
+ *
+ * @param <T> Class member
+ */
+// sonar complains about underscores in variable names
+@SuppressWarnings("java:S116")
+public abstract class AbstractQpidTransformer<T extends MemberDescription> 
implements QpidTransformer<T>
+{
+    protected static final Boolean FALSE = Boolean.FALSE;
+    protected static final Boolean TRUE = Boolean.TRUE;
+
+    /**
+     * Type names
+     */
+    protected static final String ARRAYS = Type.getInternalName(Arrays.class);
+    protected static final String BOOLEAN = 
Type.getInternalName(Boolean.class);
+    protected static final String BYTE = Type.getInternalName(Byte.class);
+    protected static final String CALLSITE = 
Type.getInternalName(CallSite.class);
+    protected static final String CHARACTER = 
Type.getInternalName(Character.class);
+    protected static final String CLASS = Type.getInternalName(Class.class);
+    protected static final String CO = 
"org/apache/qpid/server/model/ConfiguredObject";
+    protected static final String CO_TYPE_REGISTRY = 
"org/apache/qpid/server/model/ConfiguredObjectTypeRegistry";
+    protected static final String COLLECTOR = 
Type.getInternalName(Collector.class);
+    protected static final String COLLECTORS = 
Type.getInternalName(Collectors.class);
+    protected static final String DOUBLE = Type.getInternalName(Double.class);
+    protected static final String FIELD = Type.getInternalName(Field.class);
+    protected static final String FLOAT = Type.getInternalName(Float.class);
+    protected static final String FUNCTION = 
Type.getInternalName(Function.class);
+    protected static final String INTEGER = 
Type.getInternalName(Integer.class);
+    protected static final String LAMBDAMETAFACTORY = 
Type.getInternalName(LambdaMetafactory.class);
+    protected static final String LONG = Type.getInternalName(Long.class);
+    protected static final String LOOKUP = 
Type.getInternalName(MethodHandles.Lookup.class);
+    protected static final String MAP = Type.getInternalName(Map.class);
+    protected static final String METHOD = Type.getInternalName(Method.class);
+    protected static final String METHOD_HANDLE = 
Type.getInternalName(MethodHandle.class);
+    protected static final String METHOD_HANDLES = 
Type.getInternalName(MethodHandles.class);
+    protected static final String METHODTYPE = 
Type.getInternalName(MethodType.class);
+    protected static final String OBJECT = Type.getInternalName(Object.class);
+    protected static final String SHORT = Type.getInternalName(Short.class);
+    protected static final String SSRE = 
"org/apache/qpid/server/util/ServerScopedRuntimeException";
+    protected static final String STREAM = Type.getInternalName(Stream.class);
+    protected static final String STRING = Type.getInternalName(String.class);
+    protected static final String STRING_BUILDER = 
Type.getInternalName(StringBuilder.class);
+    protected static final String THROWABLE = 
Type.getInternalName(Throwable.class);
+    protected static final String VOID = Type.getInternalName(Void.class);
+
+    /**
+     * Primitive types
+     */
+    protected static final String TYPE_BOOLEAN = "Z";
+    protected static final String TYPE_BYTE = "B";
+    protected static final String TYPE_CHARACTER = "C";
+    protected static final String TYPE_DOUBLE = "D";
+    protected static final String TYPE_FLOAT = "F";
+    protected static final String TYPE_INT = "I";
+    protected static final String TYPE_LONG = "J";
+    protected static final String TYPE_SHORT = "S";
+    protected static final String TYPE_VOID = "V";
+
+    private static final Map<String, Supplier<String>> TYPES = Map.of(
+            TYPE_BOOLEAN, () -> BOOLEAN,
+            TYPE_BYTE, () -> BYTE,
+            TYPE_CHARACTER, () -> CHARACTER,
+            TYPE_DOUBLE, () -> DOUBLE,
+            TYPE_FLOAT, () -> FLOAT,
+            TYPE_INT, () -> INTEGER,
+            TYPE_LONG, () -> LONG,
+            TYPE_SHORT, () -> SHORT,
+            TYPE_VOID, () -> VOID);
+
+    /**
+     * Field names
+     */
+    protected static final String FIELD_FIELD = "_field";
+    protected static final String FIELD_HASHCODE = "_hashcode";
+    protected static final String FIELD_SIGNATURE = "_signature";
+    protected static final String TYPE = "TYPE";
+
+    /**
+     * Method names
+     */
+    protected static final String APPEND = "append";
+    protected static final String APPLY = "apply";
+    protected static final String CLINIT = "<clinit>";
+    protected static final String COLLECT = "collect";
+    protected static final String FORMAT = "format";
+    protected static final String GET_CANONICAL_NAME = "getCanonicalName";
+    protected static final String GET_DECLARING_CLASS = "getDeclaringClass";
+    protected static final String GET_NAME = "getName";
+    protected static final String GET_PARAM_TYPES = "getParameterTypes";
+    protected static final String GET_SIMPLE_NAME = "getSimpleName";
+    protected static final String HASHCODE = "hashCode";
+    protected static final String INIT = "<init>";
+    protected static final String INVOKE = "invoke";
+    protected static final String INVOKE_EXACT = "invokeExact";
+    protected static final String JOINING = "joining";
+    protected static final String METAFACTORY = "metafactory";
+    protected static final String METHOD_LOOKUP = "lookup";
+    protected static final String METHOD_MAP = "map";
+    protected static final String SET = "set";
+    protected static final String SET_ACCESSIBLE = "setAccessible";
+    protected static final String METHOD_STREAM = "stream";
+    protected static final String TO_STRING = "toString";
+    protected static final String VALUE_OF = "valueOf";
+
+    /**
+     * Method descriptors
+     */
+    protected static final String NO_ARGS = "()%s";
+    protected static final String ONE_ARG = "(%s)%s";
+    protected static final String DESC_APPEND = String.format(ONE_ARG, 
referenceName(STRING), referenceName(STRING_BUILDER));
+    protected static final String DESC_APPLY = String.format("()%s", 
referenceName(FUNCTION));
+    protected static final String DESC_COLLECT = String.format(ONE_ARG, 
referenceName(COLLECTOR), referenceName(OBJECT));
+    protected static final String DESC_GET_CLASS = String.format(NO_ARGS, 
referenceName(CLASS));
+    protected static final String DESC_GET_STRING = String.format(NO_ARGS, 
referenceName(STRING));
+    protected static final String DESC_GET_PARAM_TYPES = 
String.format("()[%s", referenceName(CLASS));
+    protected static final String DESC_HASHCODE = "()I";
+    protected static final String DESC_JOINING = String.format("()%s", 
referenceName(COLLECTOR));
+    protected static final String DESC_METAFACTORY = 
String.format("(%s%s%s%s%s%s)%s",
+                                                                   
referenceName(LOOKUP),
+                                                                   
referenceName(STRING),
+                                                                   
referenceName(METHODTYPE),
+                                                                   
referenceName(METHODTYPE),
+                                                                   
referenceName(METHOD_HANDLE),
+                                                                   
referenceName(METHODTYPE),
+                                                                   
referenceName(CALLSITE));
+    protected static final String DESC_LOOKUP = String.format(NO_ARGS, 
referenceName(LOOKUP));
+    protected static final String DESC_MAP = String.format(ONE_ARG, 
referenceName(FUNCTION), referenceName(STREAM));
+    protected static final String DESC_EXCEPTION1 = String.format("(%s)V", 
referenceName(STRING));
+    protected static final String DESC_EXCEPTION2 = String.format("(%s%s)V", 
referenceName(STRING), referenceName(THROWABLE));
+    protected static final String DESC_FORMAT = String.format("(%s[%s)%s", 
referenceName(STRING), referenceName(OBJECT), referenceName(STRING));
+    protected static final String DESC_FORNAME = String.format(ONE_ARG, 
referenceName(STRING), referenceName(CLASS));
+    protected static final String DESC_GET_DECLARED_METHOD =
+            String.format("(%s[%s)%s", referenceName(STRING), 
referenceName(CLASS), referenceName(METHOD));
+    protected static final String DESC_PRIVATE_LOOKUP = 
String.format("(%s%s)%s", referenceName(CLASS), referenceName(LOOKUP), 
referenceName(LOOKUP));
+    protected static final String DESC_SET = String.format("(%s%s)V", 
referenceName(OBJECT), referenceName(OBJECT));
+    protected static final String DESC_STREAM = String.format("([%s)%s", 
referenceName(OBJECT), referenceName(STREAM));
+    protected static final String DESC_UNREFLECT = String.format(ONE_ARG, 
referenceName(METHOD), referenceName(METHOD_HANDLE));
+
+    /**
+     * Types
+     */
+    protected static final Type TYPE_LAMBDAMETAFACTORY =
+            Type.getType(String.format("(%s)%s", referenceName(OBJECT), 
referenceName(OBJECT)));
+    protected static final Type TYPE_GET_SIMPLE_NAME =
+            Type.getType(String.format("(%s)%s", referenceName(CLASS), 
referenceName(STRING)));
+
+    /**
+     * Handles
+     */
+    protected static final Handle HANDLE_LAMBDAMETAFACTORY =
+            new Handle(Opcodes.H_INVOKESTATIC, LAMBDAMETAFACTORY, METAFACTORY, 
DESC_METAFACTORY, FALSE);
+    protected static final Handle HANDLE_GET_SIMPLE_NAME =
+            new Handle(Opcodes.H_INVOKEVIRTUAL, CLASS, GET_SIMPLE_NAME, 
DESC_GET_STRING, FALSE);
+
+    @SuppressWarnings("java:S1181")
+    public byte[] generate(byte[] bytes)
+    {
+        try
+        {
+            final ClassReader classReader = new ClassReader(bytes);
+            final ClassWriter cw = new ClassWriter(classReader, 
ClassWriter.COMPUTE_FRAMES);
+            classReader.accept(getTransformer(cw), ClassReader.EXPAND_FRAMES);
+            return cw.toByteArray();
+        }
+        catch (Throwable t)
+        {
+            getLogger().error("Error during code instrumentation", t);
+            throw new ServerScopedRuntimeException(t);
+        }
+    }
+
+    protected static String referenceName(final String type)
+    {
+        if (type.charAt(0) == '[' || (type.charAt(0) == 'L' && 
type.endsWith(";")))
+        {
+            return type;
+        }
+        return TYPES.containsKey(type) ? type : String.format("L%s;", 
type.replace('.', '/'));
+    }
+
+    protected static String sig(final MethodDescription methodDescription)
+    {
+        return "(" + referenceName(methodDescription.getDeclaringClass()) +
+               methodDescription.getParameters()
+                                .stream()
+                                .map(AbstractQpidTransformer::referenceName)
+                                .collect(Collectors.joining()) + ")" +
+               referenceName(methodDescription.getReturnType());
+    }
+
+    protected void visitMethodHandleFields(int size, final ClassWriter cw)
+    {
+        final int access = Opcodes.ACC_FINAL + Opcodes.ACC_STATIC;
+        for (int i = 0; i < size; i++)
+        {
+            cw.visitField(access, "MH_" + i, referenceName(METHOD_HANDLE), 
null, null).visitEnd();
+        }
+    }
+
+    protected void visitStaticInitializer(final List<MethodDescription> 
methods,
+                                          final String internalClassName,
+                                          final String className,
+                                          final ClassWriter cw)
+    {
+        final MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, CLINIT, 
"()V", null, null);
+        mv.visitCode();
+
+        int varIndex = 0;
+
+        final Label tryStart = new Label();
+        final Label tryEnd = new Label();
+        final Label catchStart = new Label();
+        mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, THROWABLE);
+        mv.visitLabel(tryStart);
+
+        // MethodHandles.Lookup lookup = MethodHandles.lookup();
+        mv.visitMethodInsn(Opcodes.INVOKESTATIC, METHOD_HANDLES, 
METHOD_LOOKUP, DESC_LOOKUP, FALSE);
+        mv.visitVarInsn(Opcodes.ASTORE, varIndex);
+        final int lookupIndex = varIndex;
+
+        String previousClassName = "";
+        int declaringClassIndex = varIndex;
+        for (int i = 0; i < methods.size(); i++)
+        {
+            final MethodDescription method = methods.get(i);
+            final String declaringClass = 
method.getDeclaringClass().replace('/', '.');
+            final String methodName = method.getName();
+
+            if (!Objects.equals(previousClassName, declaringClass))
+            {
+                // Class type = Class.forName(...)
+                mv.visitLdcInsn(declaringClass);
+                mv.visitMethodInsn(Opcodes.INVOKESTATIC, CLASS, "forName", 
DESC_FORNAME, FALSE);
+                varIndex++;
+                mv.visitVarInsn(Opcodes.ASTORE, varIndex);
+                declaringClassIndex = varIndex;
+
+                // lookup = MethodHandles.privateLookupIn(declaringClass, 
lookup);
+                mv.visitVarInsn(Opcodes.ALOAD, declaringClassIndex);
+                mv.visitVarInsn(Opcodes.ALOAD, lookupIndex);
+                mv.visitMethodInsn(Opcodes.INVOKESTATIC, METHOD_HANDLES, 
"privateLookupIn", DESC_PRIVATE_LOOKUP, FALSE);
+                mv.visitVarInsn(Opcodes.ASTORE, lookupIndex);
+            }
+
+            mv.visitIntInsn(Opcodes.BIPUSH, method.getParameterCount());
+            mv.visitTypeInsn(Opcodes.ANEWARRAY, CLASS);
+            varIndex++;
+            mv.visitVarInsn(Opcodes.ASTORE, varIndex);
+            final int paramsIndex = varIndex;
+
+            for (int j = 0; j < method.getParameterCount(); j++)
+            {
+                mv.visitVarInsn(Opcodes.ALOAD, paramsIndex);
+                mv.visitIntInsn(Opcodes.BIPUSH, j);
+                autoboxFieldIfNeeded(method.getParameter(j), mv);
+                mv.visitVarInsn(Opcodes.ASTORE, varIndex + 1 + j);
+                mv.visitVarInsn(Opcodes.ALOAD, varIndex + 1 + j);
+                mv.visitInsn(Opcodes.AASTORE);
+            }
+            varIndex = varIndex + method.getParameterCount() + 1;
+
+            mv.visitVarInsn(Opcodes.ALOAD, declaringClassIndex);
+            mv.visitLdcInsn(methodName);
+            mv.visitVarInsn(Opcodes.ALOAD, paramsIndex);
+
+            // Method getter = type.getDeclaredMethod(...)
+            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLASS, 
"getDeclaredMethod", DESC_GET_DECLARED_METHOD, FALSE);
+            varIndex++;
+            mv.visitVarInsn(Opcodes.ASTORE, varIndex);
+
+            // getter.setAccessible(true)
+            mv.visitVarInsn(Opcodes.ALOAD, varIndex);
+            mv.visitInsn(Opcodes.ICONST_1);
+            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD, SET_ACCESSIBLE, 
"(Z)V", FALSE);
+
+            // MH_xx = methodHandle.unreflect(method)
+            mv.visitVarInsn(Opcodes.ALOAD, lookupIndex);
+            mv.visitVarInsn(Opcodes.ALOAD, varIndex);
+            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, LOOKUP, "unreflect", 
DESC_UNREFLECT, FALSE);
+            mv.visitFieldInsn(Opcodes.PUTSTATIC, internalClassName, "MH_" + i, 
referenceName(METHOD_HANDLE));
+
+            previousClassName = declaringClass;
+        }
+
+        mv.visitLabel(tryEnd);
+        mv.visitInsn(Opcodes.RETURN);
+
+        visitCatchBlock("Failed to initialize getters for " + className, 
catchStart, varIndex, mv);
+
+        mv.visitMaxs(0, 0);
+        mv.visitEnd();
+    }
+
+    protected void visitCatchBlock(final String errorMessage,
+                                   final Label catchStart,
+                                   int varIndex,
+                                   final MethodVisitor mv)
+    {
+        mv.visitLabel(catchStart);
+        mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{THROWABLE});
+        mv.visitVarInsn(Opcodes.ASTORE, varIndex);
+        mv.visitTypeInsn(Opcodes.NEW, SSRE);
+        mv.visitInsn(Opcodes.DUP);
+        mv.visitLdcInsn(errorMessage);
+        mv.visitVarInsn(Opcodes.ALOAD, varIndex);
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, SSRE, INIT, DESC_EXCEPTION2, 
FALSE);
+        mv.visitInsn(Opcodes.ATHROW);
+
+        final Label catchEnd = new Label();
+        mv.visitLabel(catchEnd);
+
+        mv.visitLocalVariable("throwable", referenceName(THROWABLE), null, 
catchStart, catchEnd, varIndex);
+    }
+
+    protected void autoboxFieldIfNeeded(final String type, final MethodVisitor 
visitor)
+    {
+        if (TYPES.containsKey(type) && !TYPE_VOID.equals(type))
+        {
+            visitor.visitFieldInsn(Opcodes.GETSTATIC, TYPES.get(type).get(), 
TYPE, referenceName(CLASS));
+        }
+        else
+        {
+            visitor.visitLdcInsn(Type.getType(autoboxType(type)));
+        }
+    }
+
+    protected void autoboxIfNeeded(final String in, final String out, final 
MethodVisitor visitor)
+    {
+        unboxOrAutobox(in, out, TYPE_BOOLEAN,"booleanValue", visitor);
+        unboxOrAutobox(in, out, TYPE_BYTE, "byteValue", visitor);
+        unboxOrAutobox(in, out, TYPE_CHARACTER, "charValue", visitor);
+        unboxOrAutobox(in, out, TYPE_DOUBLE, "doubleValue", visitor);
+        unboxOrAutobox(in, out, TYPE_FLOAT, "floatValue", visitor);
+        unboxOrAutobox(in, out, TYPE_INT, "intValue", visitor);
+        unboxOrAutobox(in, out, TYPE_LONG, "longValue", visitor);
+        unboxOrAutobox(in, out, TYPE_SHORT, "shortValue", visitor);
+    }
+
+    private void unboxOrAutobox(final String in,
+                                final String out,
+                                final String type,
+                                final String name,
+                                final MethodVisitor methodVisitor)
+    {
+        final String wrapper = TYPES.get(type).get();
+        if (wrapper.equals(in) && type.equals(out))
+        {
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, wrapper, 
name, "()" + type, FALSE);
+        }
+        else if (type.equals(in) && wrapper.equals(out))
+        {
+            final String desc = "(" + type +")L" + wrapper + ";";
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, wrapper, 
VALUE_OF, desc, FALSE);
+        }
+    }
+
+    /**
+     * Returns the appropriate autoboxing type.
+     */
+    protected String autoboxType(final String unboxed)
+    {
+        return TYPES.getOrDefault(unboxed, () -> referenceName(unboxed)).get();
+    }
+
+    protected Map<String, AttributeStackAddress> 
createAttributesStackMap(final List<? extends MemberDescription> methods)
+    {
+        return methods.stream()
+                .collect(Collectors.toMap(
+                        MemberDescription::getSignature,
+                        method -> new 
AttributeStackAddress(method.getSignature().hashCode()),
+                        (key1, key2) -> key2));
+    }
+
+    /**
+     * Stack map address for a particular attribute
+     */
+    protected static class AttributeStackAddress implements 
Comparable<AttributeStackAddress>
+    {
+        private final Label label;
+        private final int hash;
+
+        private AttributeStackAddress(final int hash)
+        {
+            this.label = new Label();
+            this.hash = hash;
+        }
+
+        protected Label getLabel()
+        {
+            return label;
+        }
+
+        private int getHash()
+        {
+            return hash;
+        }
+
+        @Override
+        public int compareTo(final AttributeStackAddress attributeStackAddress)
+        {
+            return Integer.compare(hash, attributeStackAddress.hash);
+        }
+
+        @Override
+        public boolean equals(final Object o)
+        {
+            if (this == o)
+            {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass())
+            {
+                return false;
+            }
+            final AttributeStackAddress that = (AttributeStackAddress) o;
+            return hash == that.hash;
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return Objects.hash(hash);
+        }
+    }
+
+    protected void fillHashesAndLabels(final Map<String, 
AttributeStackAddress> stackMap,
+                                       final int[] hashes,
+                                       final Label[] switchJumpLabels)
+    {
+        final List<AttributeStackAddress> stackAddresses = new 
ArrayList<>(stackMap.values());
+        Collections.sort(stackAddresses);
+        for (int i = 0; i < stackAddresses.size(); i++)
+        {
+            final AttributeStackAddress propertyStackAddress = 
stackAddresses.get(i);
+            hashes[i] = propertyStackAddress.getHash();
+            switchJumpLabels[i] = propertyStackAddress.getLabel();
+        }
+    }
+
+    /**
+     * Copies constructor code and adds at the end initialization of variables:
+     * <p>
+     * _signature = getter.getDeclaringClass().getName() + "#" + 
getter.getName();
+     * _hashcode = _signature.hashcode();
+     */
+    protected static class ConstructorTransformer extends MethodVisitor
+    {
+        private final String _internalClassName;
+        private final int _methodIndex;
+
+        protected ConstructorTransformer(MethodVisitor delegate, String 
internalClassName, int methodIndex)
+        {
+            super(Opcodes.ASM9, delegate);
+            _internalClassName = internalClassName;
+            _methodIndex = methodIndex;
+        }
+
+        @Override()
+        public void visitInsn(int opcode)
+        {
+            if (opcode == Opcodes.RETURN)
+            {
+                generateSignature(super.mv, _methodIndex, _internalClassName, 
FIELD_SIGNATURE);
+                generateHashCode(super.mv, _internalClassName, 
FIELD_SIGNATURE, FIELD_HASHCODE);
+            }
+            super.visitInsn(opcode);
+        }
+    }
+
+    protected static void generateSignature(final MethodVisitor mv,
+                                            final int methodIndex,
+                                            final String internalClassName,
+                                            final String signatureField)
+    {
+        // _signature = method.getDeclaringClass().getName() +
+        // "#" + method.getName() +
+        // 
Arrays.stream(method.getParameterTypes()).map(Class::getSimpleName).collect(Collectors.joining());
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitTypeInsn(Opcodes.NEW, STRING_BUILDER);
+        mv.visitInsn(Opcodes.DUP);
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, STRING_BUILDER, INIT, "()V", 
FALSE);
+        mv.visitVarInsn(Opcodes.ALOAD, methodIndex);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD, GET_DECLARING_CLASS, 
DESC_GET_CLASS, FALSE);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLASS, GET_CANONICAL_NAME, 
DESC_GET_STRING, FALSE);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING_BUILDER, APPEND, 
DESC_APPEND, FALSE);
+        mv.visitLdcInsn("#");
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING_BUILDER, APPEND, 
DESC_APPEND, FALSE);
+        mv.visitVarInsn(Opcodes.ALOAD, methodIndex);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD, GET_NAME, 
DESC_GET_STRING, FALSE);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING_BUILDER, APPEND, 
DESC_APPEND, FALSE);
+        mv.visitVarInsn(Opcodes.ALOAD, methodIndex);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD, GET_PARAM_TYPES, 
DESC_GET_PARAM_TYPES, FALSE);
+        mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAYS, METHOD_STREAM, 
DESC_STREAM, FALSE);
+        mv.visitInvokeDynamicInsn(APPLY, DESC_APPLY, HANDLE_LAMBDAMETAFACTORY,
+                                     TYPE_LAMBDAMETAFACTORY, 
HANDLE_GET_SIMPLE_NAME, TYPE_GET_SIMPLE_NAME);
+        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, STREAM, METHOD_MAP, 
DESC_MAP, TRUE);
+        mv.visitMethodInsn(Opcodes.INVOKESTATIC, COLLECTORS, JOINING, 
DESC_JOINING, FALSE);
+        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, STREAM, COLLECT, 
DESC_COLLECT, TRUE);
+        mv.visitTypeInsn(Opcodes.CHECKCAST, STRING);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING_BUILDER, APPEND, 
DESC_APPEND, FALSE);
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING_BUILDER, TO_STRING, 
DESC_GET_STRING, FALSE);
+        mv.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, signatureField, 
referenceName(STRING));
+    }
+
+    protected static void generateHashCode(final MethodVisitor mv,
+                                           final String internalClassName,
+                                           final String signatureField,
+                                           final String hashCodeField)
+    {
+        // _hashcode = _signature.hashcode();
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitFieldInsn(Opcodes.GETFIELD, internalClassName, signatureField, 
referenceName(STRING));
+        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING, HASHCODE, 
DESC_HASHCODE, FALSE);
+        mv.visitFieldInsn(Opcodes.PUTFIELD, internalClassName, hashCodeField, 
TYPE_INT);
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectMethodAttributeOrStatisticTransformer.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectMethodAttributeOrStatisticTransformer.java
new file mode 100644
index 0000000000..51ef91aade
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectMethodAttributeOrStatisticTransformer.java
@@ -0,0 +1,279 @@
+/*
+ * 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.qpid.server.instrumentation.transformer;
+
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.instrumentation.metadata.MethodDescription;
+
+/**
+ * Transforms 
org.apache.qpid.server.model.ConfiguredObjectMethodAttributeOrStatistic class.
+ * <p>
+ * Class structure after transformation:
+ * <pre>
+ * abstract class ConfiguredObjectMethodAttributeOrStatistic
+ * {
+ *     static final MethodHandle MH_1;
+ *     static final MethodHandle MH_2;
+ *     static final MethodHandle MH_3;
+ *     private final String _signature;
+ *     private final int _hashcode;
+ *     static
+ *     {
+ *         MethodHandles.Lookup lookup = MethodHandles.lookup();
+ *         Class declaringClass = 
Class.forName("org.apache.qpid.server.model.ConfiguredObject");
+ *         Method method = declaringClass.getDeclaredMethod("getId");
+ *         method.setAccessible(true);
+ *         MH_1 = lookup.unreflect(method);
+ *         method = declaringClass.getDeclaredMethod("getName");
+ *         method.setAccessible(true);
+ *         MH_2 = lookup.unreflect(method);
+ *         method = declaringClass.getDeclaredMethod("getState");
+ *         method.setAccessible(true);
+ *         MH_3 = lookup.unreflect(method);
+ *     }
+ *     ConfiguredObjectMethodAttributeOrStatistic(final Method method)
+ *     {
+ *         _signature = method.getDeclaringClass() + "#"  + method.getName() +
+ *             
Arrays.stream(getter.getParameterClasses()).map(Class::getSimpleName()).collect(Collectors.joining());
+ *         _hashcode = _signature.hashCode();
+ *     }
+ *     public T getValue(C configuredObject)
+ *     {
+ *         switch(_hashcode)
+ *         {
+ *             case 1:
+ *                 return MH_1.invokeExact(configuredObject);
+ *             case 2:
+ *                 return MH_2.invokeExact(configuredObject);
+ *             case 3:
+ *                 return MH_2.invokeExact(configuredObject);
+ *             default:
+ *                 throw new UnsupportedOperationException("No accessor 
found");
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+// sonar complains about underscores in variable names
+@SuppressWarnings("java:S116")
+public class ConfiguredObjectMethodAttributeOrStatisticTransformer<T extends 
MethodDescription> extends AbstractQpidTransformer<T>
+        implements QpidTransformer<T>
+{
+    /** Target class name */
+    private static final String CLASS_NAME = 
"org.apache.qpid.server.model.ConfiguredObjectMethodAttributeOrStatistic";
+
+    /** Internal class name */
+    private static final String INTERNAL_CLASS_NAME = 
"org/apache/qpid/server/model/ConfiguredObjectMethodAttributeOrStatistic";
+
+    /** Name of the getValue() method */
+    private static final String GET_VALUE = "getValue";
+
+    /** Constructor descriptor */
+    private static final String DESC_CONSTRUCTOR = String.format("(%s)V", 
referenceName(METHOD));
+
+    /** Descriptor of getValue() method */
+    private static final String DESC_GET_VALUE = String.format(ONE_ARG, 
referenceName(CO), referenceName(OBJECT));
+
+    /** Logger */
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ConfiguredObjectMethodAttributeOrStatisticTransformer.class);
+
+    /** List of methods @DerivedAttribute, @ManagedAttribute, 
@ManagedStatistic */
+    final List<T> _methods;
+
+    public ConfiguredObjectMethodAttributeOrStatisticTransformer(final List<T> 
methods)
+    {
+        super();
+        _methods = new ArrayList<>(methods);
+    }
+
+    @Override
+    public Logger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    public List<T> getMemberDescriptions()
+    {
+        return new ArrayList<>(_methods);
+    }
+
+    @Override
+    public ClassVisitor getTransformer(final ClassVisitor cv)
+    {
+        return new Transformer(cv, getMemberDescriptions());
+    }
+
+    /**
+     * Copies class code adding custom logic
+     */
+    private class Transformer extends ClassVisitor
+    {
+        private final List<MethodDescription> _methods;
+
+        @SuppressWarnings("unchecked")
+        private Transformer(final ClassVisitor cv, final List<T> methods)
+        {
+            super(Opcodes.ASM9, cv);
+            _methods = (List<MethodDescription>) methods;
+        }
+
+        /**
+         * When reaching end of the class, static final MethodHandle variables 
are generated as well as additional
+         * fields _signature and _hashcode.
+         */
+        public void visitEnd()
+        {
+            visitMethodHandleFields(_methods.size(), (ClassWriter) cv);
+            final int access = Opcodes.ACC_PROTECTED | Opcodes.ACC_FINAL;
+            visitField(access, FIELD_SIGNATURE, referenceName(STRING), null, 
null).visitEnd();
+            visitField(access, FIELD_HASHCODE, TYPE_INT, null, 
null).visitEnd();
+            visitStaticInitializer(_methods, INTERNAL_CLASS_NAME, CLASS_NAME, 
(ClassWriter) cv);
+        }
+
+        /**
+         * Creates MethodVisitor which copies all class methods but 
constructor and getValue() method
+         *
+         * @param access     the method's access flags. This parameter also 
indicates if
+         *                   the method is synthetic and/or deprecated.
+         * @param name       the method's name.
+         * @param desc       the method's descriptor
+         * @param signature  the method's signature. May be {@literal null} if 
the method parameters,
+         *                   return type and exceptions do not use generic 
types.
+         * @param exceptions the internal names of the method's exception 
classes
+         * @return MethodVisitor instance
+         */
+        @Override
+        public MethodVisitor visitMethod(final int access,
+                                         final String name,
+                                         final String desc,
+                                         final String signature,
+                                         final String[] exceptions)
+        {
+            MethodVisitor mv = super.visitMethod(access, name, desc, 
signature, exceptions);
+
+            // add custom logic for constructor
+            if (name.equals(INIT) && desc.equals(DESC_CONSTRUCTOR))
+            {
+                final int methodIndex = 1; // index of method variable in 
constructor
+                mv = new ConstructorTransformer(mv, INTERNAL_CLASS_NAME, 
methodIndex);
+            }
+
+            // add custom logic for getValue()
+            if (name.equals(GET_VALUE) && desc.equals(DESC_GET_VALUE))
+            {
+                mv = new GetValueTransformer(mv, _methods);
+            }
+
+            return mv;
+        }
+    }
+
+    class GetValueTransformer extends MethodVisitor
+    {
+        private final MethodVisitor _target;
+        private final List<MethodDescription> _methods;
+
+        public GetValueTransformer(final MethodVisitor delegate, final 
List<MethodDescription> methods)
+        {
+            super(Opcodes.ASM9, null);
+            _target = delegate;
+            _methods = methods;
+        }
+
+        @Override()
+        public void visitCode()
+        {
+            _target.visitCode();
+
+            _target.visitVarInsn(Opcodes.ALOAD, 0);
+            _target.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
FIELD_HASHCODE, TYPE_INT);
+            _target.visitVarInsn(Opcodes.ISTORE, 2);
+
+            final Map<String, AttributeStackAddress> stackMap = 
createAttributesStackMap(_methods);
+            final int[] hashes = new int[stackMap.size()];
+            final Label[] switchJumpLabels = new Label[stackMap.size()];
+            fillHashesAndLabels(stackMap, hashes, switchJumpLabels);
+
+            final Label label = new Label();
+
+            _target.visitVarInsn(Opcodes.ILOAD, 2);
+            _target.visitLookupSwitchInsn(label, hashes, switchJumpLabels);
+
+            for (int i = 0; i < _methods.size(); i++)
+            {
+                final MethodDescription method = _methods.get(i);
+                final String declaringClass = method.getDeclaringClass();
+                final String returnType = method.getReturnType();
+                final String signature = String.format(ONE_ARG, 
referenceName(declaringClass), referenceName(returnType));
+                
_target.visitLabel(stackMap.get(method.getSignature()).getLabel());
+                _target.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+                _target.visitFieldInsn(Opcodes.GETSTATIC, INTERNAL_CLASS_NAME, 
"MH_" + i, referenceName(METHOD_HANDLE));
+                _target.visitVarInsn(Opcodes.ALOAD, 1);
+                _target.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD_HANDLE, 
INVOKE_EXACT, signature, FALSE);
+                if (method.returnsPrimitive())
+                {
+                    autoboxIfNeeded(returnType, autoboxType(returnType), 
_target);
+                }
+                _target.visitInsn(Opcodes.ARETURN);
+            }
+
+            _target.visitLabel(label);
+            _target.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+
+            _target.visitVarInsn(Opcodes.ALOAD, 0);
+            _target.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
FIELD_SIGNATURE, referenceName(STRING));
+            _target.visitVarInsn(Opcodes.ASTORE, 3);
+            _target.visitTypeInsn(Opcodes.NEW, SSRE);
+            _target.visitInsn(Opcodes.DUP);
+            _target.visitLdcInsn("No accessor to get attribute %s from object 
%s");
+            _target.visitInsn(Opcodes.ICONST_2);
+            _target.visitTypeInsn(Opcodes.ANEWARRAY, OBJECT);
+            _target.visitInsn(Opcodes.DUP);
+            _target.visitInsn(Opcodes.ICONST_0);
+            _target.visitVarInsn(Opcodes.ALOAD, 3);
+            _target.visitInsn(Opcodes.AASTORE);
+            _target.visitInsn(Opcodes.DUP);
+            _target.visitInsn(Opcodes.ICONST_1);
+            _target.visitVarInsn(Opcodes.ALOAD, 1);
+            _target.visitInsn(Opcodes.AASTORE);
+            _target.visitMethodInsn(Opcodes.INVOKESTATIC, STRING, FORMAT, 
DESC_FORMAT, FALSE);
+            _target.visitMethodInsn(Opcodes.INVOKESPECIAL, SSRE, INIT, 
DESC_EXCEPTION1, FALSE);
+            _target.visitInsn(Opcodes.ATHROW);
+
+            _target.visitInsn(Opcodes.RETURN);
+
+            _target.visitMaxs(0, 0);
+            _target.visitEnd();
+        }
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectMethodOperationTransformer.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectMethodOperationTransformer.java
new file mode 100644
index 0000000000..e7f31602aa
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectMethodOperationTransformer.java
@@ -0,0 +1,366 @@
+/*
+ * 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.qpid.server.instrumentation.transformer;
+
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.server.instrumentation.metadata.MethodDescription;
+
+/**
+ * Transforms org.apache.qpid.server.model.ConfiguredObjectMethodOperation 
class.
+ * <p>
+ * Class structure after transformation:
+ * <pre>
+ * abstract class ConfiguredObjectMethodOperation
+ * {
+ *     static final MethodHandle MH_1;
+ *     static final MethodHandle MH_2;
+ *     static final MethodHandle MH_3;
+ *     private final String _signature;
+ *     private final int _hashcode;
+ *
+ *     static
+ *     {
+ *         MethodHandles.Lookup lookup = MethodHandles.lookup();
+ *         Class declaringClass = 
Class.forName("org.apache.qpid.server.model.ConfiguredObject");
+ *         Method method = declaringClass.getDeclaredMethod("getId");
+ *         method.setAccessible(true);
+ *         MH_1 = lookup.unreflect(method);
+ *         method = declaringClass.getDeclaredMethod("getName");
+ *         method.setAccessible(true);
+ *         MH_2 = lookup.unreflect(method);
+ *         method = declaringClass.getDeclaredMethod("getState");
+ *         method.setAccessible(true);
+ *         MH_3 = lookup.unreflect(method);
+ *     }
+ *
+ *     ConfiguredObjectMethodOperation(final Class<C> clazz,
+ *                                     final Method operation,
+ *                                     final ConfiguredObjectTypeRegistry 
typeRegistry)
+ *     {
+ *         // original code
+ *         // original code
+ *         // original code
+ *
+ *         _signature = method.getDeclaringClass() + "#"  + method.getName() +
+ *                
Arrays.stream(getter.getParameterClasses()).map(Class::getSimpleName()).collect(Collectors.joining());
+ *         _hashcode = _signature.hashCode();
+ *     }
+ *
+ *     public Object perform(C subject, Map<String, Object> parameters)
+ *     {
+ *         //
+ *         // original code
+ *         //
+ *
+ *         // line "return _operation.invoke(subject, paramValues);" is 
replaced with following call:
+ *
+ *         switch(_hashcode)
+ *         {
+ *             case 1:
+ *                 return MH_1.invokeExact(subject, paramValues[0]);
+ *             case 2:
+ *                 MH_2.invokeExact(subject, paramValues[0], paramValues[1], 
paramValues[2]);
+ *                 return null;
+ *             case 3:
+ *                 return MH_2.invokeExact(subject, paramValues[0]);
+ *             default:
+ *                 throw new UnsupportedOperationException("No operation 
found");
+ *         }
+ *
+ *         //
+ *         // original code
+ *         //
+ *     }
+ * }
+ * </pre>
+ */
+// sonar complains about underscores in variable names
+@SuppressWarnings("java:S116")
+public class ConfiguredObjectMethodOperationTransformer<T extends 
MethodDescription> extends AbstractQpidTransformer<T>
+        implements QpidTransformer<T>
+{
+    /** Target class name */
+    private static final String CLASS_NAME = 
"org.apache.qpid.server.model.ConfiguredObjectMethodOperation";
+
+    /** Internal class name */
+    private static final String INTERNAL_CLASS_NAME = 
"org/apache/qpid/server/model/ConfiguredObjectMethodOperation";
+
+    /** Constructor descriptor */
+    private static final String DESC_CONSTRUCTOR =
+            String.format("(%s%s%s)V", referenceName(CLASS), 
referenceName(METHOD), referenceName(CO_TYPE_REGISTRY));
+
+    /** Name of method ConfiguredObjectMethodOperation#perform() */
+    private static final String PERFORM = "perform";
+
+    /** Descriptor of method ConfiguredObjectMethodOperation#perform() */
+    private static final String DESC_PERFORM = String.format("(%s%s)%s", 
referenceName(CO), referenceName(MAP), referenceName(OBJECT));
+
+    /** Logger */
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ConfiguredObjectMethodOperationTransformer.class);
+
+    /** List of methods @ManagedOperation */
+    private final List<T> _methods;
+
+    /**
+     * Constructor stores method list
+     *
+     * @param methods Methods to be handled
+     */
+    public ConfiguredObjectMethodOperationTransformer(final List<T> methods)
+    {
+        super();
+        _methods = new ArrayList<>(methods);
+    }
+
+    @Override
+    public Logger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    public List<T> getMemberDescriptions()
+    {
+        return new ArrayList<>(_methods);
+    }
+
+    @Override
+    public ClassVisitor getTransformer(final ClassVisitor cv)
+    {
+        return new Transformer(cv, getMemberDescriptions());
+    }
+
+    /**
+     * Copies class code adding custom logic
+     */
+    private class Transformer extends ClassVisitor
+    {
+        private final List<MethodDescription> _methods;
+
+        @SuppressWarnings("unchecked")
+        private Transformer(final ClassVisitor cv, final List<T> methods)
+        {
+            super(Opcodes.ASM9, cv);
+            _methods = (List<MethodDescription>) methods;
+        }
+
+        /**
+         * When reaching end of the class, static final MethodHandle variables 
are generated as well as additional
+         * fields _signature and _hashcode.
+         */
+        public void visitEnd()
+        {
+            visitMethodHandleFields(_methods.size(), (ClassWriter) cv);
+            final int access = Opcodes.ACC_PROTECTED | Opcodes.ACC_FINAL;
+            visitField(access, FIELD_SIGNATURE, referenceName(STRING), null, 
null).visitEnd();
+            visitField(access, FIELD_HASHCODE, TYPE_INT, null, 
null).visitEnd();
+            visitStaticInitializer(_methods, INTERNAL_CLASS_NAME, CLASS_NAME, 
(ClassWriter) cv);
+        }
+
+        /**
+         * Creates MethodVisitor which copies all class methods but 
constructor and
+         * ConfiguredObjectMethodOperation#perform() method
+         *
+         * @param access     the method's access flags. This parameter also 
indicates if
+         *                   the method is synthetic and/or deprecated.
+         * @param name       the method's name.
+         * @param desc       the method's descriptor
+         * @param signature  the method's signature. May be {@literal null} if 
the method parameters,
+         *                   return type and exceptions do not use generic 
types.
+         * @param exceptions the internal names of the method's exception 
classes
+         * @return MethodVisitor instance
+         */
+        @Override
+        public MethodVisitor visitMethod(final int access,
+                                         final String name,
+                                         final String desc,
+                                         final String signature,
+                                         final String[] exceptions)
+        {
+            MethodVisitor mv = super.visitMethod(access, name, desc, 
signature, exceptions);
+
+            // add custom logic for constructor
+            if (name.equals(INIT) && desc.equals(DESC_CONSTRUCTOR))
+            {
+                final int methodIndex = 2; // index of method variable in 
constructor
+                mv = new ConstructorTransformer(mv, INTERNAL_CLASS_NAME, 
methodIndex);
+            }
+
+            // add custom logic for perform()
+            if (name.equals(PERFORM) && desc.equals(DESC_PERFORM))
+            {
+                mv = new PerformTransformer(mv, _methods);
+            }
+
+            return mv;
+        }
+    }
+
+    /**
+     * Copies ConfiguredObjectMethodOperation#perform() code
+     * replacing "method.invoke()" with switch / case routine calling static 
final MethodHandle based on method hashcode.
+     */
+    private class PerformTransformer extends MethodVisitor
+    {
+        private final List<MethodDescription> _methods;
+
+        private boolean skipOperations = false;
+
+        private PerformTransformer(final MethodVisitor delegate, final 
List<MethodDescription> methods)
+        {
+            super(Opcodes.ASM9, delegate);
+            _methods = methods;
+        }
+
+        @Override
+        public void visitVarInsn(final int opcode, final int index)
+        {
+            if (!skipOperations)
+            {
+                super.visitVarInsn(opcode, index);
+            }
+        }
+
+        @Override
+        public void visitFieldInsn(final int opcode, final String owner, final 
String name, final String descriptor)
+        {
+            if (!skipOperations)
+            {
+                super.visitFieldInsn(opcode, owner, name, descriptor);
+            }
+        }
+
+        @Override
+        public void visitMethodInsn(final int opcode,
+                                    final String owner,
+                                    final String name,
+                                    final String descriptor,
+                                    final boolean isInterface)
+        {
+            if (opcode == Opcodes.INVOKEVIRTUAL && 
owner.equals(INTERNAL_CLASS_NAME) && "getParameterValue".equals(name))
+            {
+                super.visitMethodInsn(opcode, owner, name, descriptor, 
isInterface);
+                skipOperations = true;
+                return;
+            }
+            if (opcode == Opcodes.INVOKEVIRTUAL && owner.equals(METHOD) && 
INVOKE.equals(name))
+            {
+                skipOperations = false;
+
+                final int configuredObjectIndex = 1;
+                final int paramsIndex = 5;
+
+                super.visitVarInsn(Opcodes.ALOAD, 0);
+                super.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
FIELD_HASHCODE, TYPE_INT);
+                super.visitVarInsn(Opcodes.ISTORE, 7);
+                final int signatureIndex = 7;
+
+                final Map<String, AttributeStackAddress> stackMap = 
createAttributesStackMap(_methods);
+                final int[] hashes = new int[stackMap.size()];
+                final Label[] switchJumpLabels = new Label[stackMap.size()];
+                fillHashesAndLabels(stackMap, hashes, switchJumpLabels);
+
+                final Label label = new Label();
+
+                super.visitVarInsn(Opcodes.ILOAD, signatureIndex);
+                super.visitLookupSwitchInsn(label, hashes, switchJumpLabels);
+
+                for (int i = 0; i < _methods.size(); i++)
+                {
+                    final MethodDescription method = _methods.get(i);
+                    final String returnType = method.getReturnType();
+                    final String signature = sig(method);
+                    
super.visitLabel(stackMap.get(method.getSignature()).getLabel());
+                    super.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+                    super.visitFieldInsn(Opcodes.GETSTATIC, 
INTERNAL_CLASS_NAME, "MH_" + i, referenceName(METHOD_HANDLE));
+                    super.visitVarInsn(Opcodes.ALOAD, configuredObjectIndex);
+
+                    for (int j = 0; j < method.getParameterCount(); j++)
+                    {
+                        String type = autoboxType(method.getParameter(j));
+                        if (type.charAt(0) == 'L' && type.endsWith(";"))
+                        {
+                            type = type.substring(1, type.length() - 1);
+                        }
+                        super.visitVarInsn(Opcodes.ALOAD, paramsIndex);
+                        super.visitIntInsn(Opcodes.BIPUSH, j);
+                        super.visitInsn(Opcodes.AALOAD);
+                        super.visitTypeInsn(Opcodes.CHECKCAST, type);
+                        autoboxIfNeeded(autoboxType(method.getParameter(j)), 
method.getParameter(j), mv);
+                    }
+
+                    super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
METHOD_HANDLE, INVOKE_EXACT, signature, FALSE);
+
+                    if (method.returnsPrimitive() && !method.returnsVoid())
+                    {
+                        autoboxIfNeeded(returnType, autoboxType(returnType), 
mv);
+                    }
+
+                    if (method.returnsVoid())
+                    {
+                        super.visitInsn(Opcodes.ACONST_NULL);
+                    }
+
+                    super.visitInsn(Opcodes.ARETURN);
+                }
+
+                super.visitLabel(label);
+                super.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+
+                /* create code: throw new UnsupportedOperationException(msg) */
+                super.visitVarInsn(Opcodes.ALOAD, 0);
+                super.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
FIELD_SIGNATURE, referenceName(STRING));
+                super.visitVarInsn(Opcodes.ASTORE, 8);
+                super.visitTypeInsn(Opcodes.NEW, SSRE);
+                super.visitInsn(Opcodes.DUP);
+                super.visitLdcInsn("Failed to call operation %s on object %s");
+                super.visitInsn(Opcodes.ICONST_2);
+                super.visitTypeInsn(Opcodes.ANEWARRAY, OBJECT);
+                super.visitInsn(Opcodes.DUP);
+                super.visitInsn(Opcodes.ICONST_0);
+                super.visitVarInsn(Opcodes.ALOAD, 8);
+                super.visitInsn(Opcodes.AASTORE);
+                super.visitInsn(Opcodes.DUP);
+                super.visitInsn(Opcodes.ICONST_1);
+                super.visitVarInsn(Opcodes.ALOAD, 1);
+                super.visitInsn(Opcodes.AASTORE);
+                super.visitMethodInsn(Opcodes.INVOKESTATIC, STRING, FORMAT, 
DESC_FORMAT, FALSE);
+                super.visitMethodInsn(Opcodes.INVOKESPECIAL, SSRE, INIT, 
DESC_EXCEPTION1, FALSE);
+                super.visitInsn(Opcodes.ATHROW);
+            }
+            else
+            {
+                super.visitMethodInsn(opcode, owner, name, descriptor, 
isInterface);
+            }
+        }
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectTypeRegistryTransformer.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectTypeRegistryTransformer.java
new file mode 100644
index 0000000000..d2a8e04e0d
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/ConfiguredObjectTypeRegistryTransformer.java
@@ -0,0 +1,427 @@
+/*
+ * 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.qpid.server.instrumentation.transformer;
+
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import 
org.apache.qpid.server.instrumentation.metadata.AutomatedFieldDescription;
+import org.apache.qpid.server.instrumentation.metadata.MethodDescription;
+
+/**
+ * Transforms 
org.apache.qpid.server.model.ConfiguredObjectTypeRegistry$AutomatedField class.
+ * <p>
+ * Class structure after transformation:
+ * <pre>
+ * static class AutomatedField
+ * {
+ *     private final Field _field;
+ *     private final Method _preSettingAction;
+ *     private final Method _postSettingAction;
+ *
+ *     private final String _preSettingSignature;
+ *     private final String _postSettingSignature;
+ *     private final int _preSettingHashCode;
+ *     private final int _postSettingHashCode;
+ *
+ *     static final MethodHandle MH_1;
+ *     static final MethodHandle MH_2;
+ *     static final MethodHandle MH_3;
+ *     static final MethodHandle MH_4;
+ *     static final MethodHandle MH_5;
+ *     static final MethodHandle MH_6;
+ *
+ *     private ConfiguredObjectTypeRegistry$AutomatedField(Field field,
+ *                                                         Method 
preSettingAction,
+ *                                                         Method 
postSettingAction)
+ *     {
+ *         // original code
+ *
+ *         if (this._preSettingAction != null)
+ *         {
+ *             this._preSettingSignature = 
preSettingAction.getDeclaringClass().getCanonicalName() + "#" +
+ *                 preSettingAction.getName() +
+ *                 
(String)Arrays.stream(preSettingAction.getParameterTypes()).map(Class::getSimpleName).collect(Collectors.joining());
+ *             this._preSettingHashcode = this._preSettingSignature.hashCode();
+ *         }
+ *
+ *         if (this._postSettingAction != null)
+ *         {
+ *             this._postSettingSignature = 
postSettingAction.getDeclaringClass().getCanonicalName() + "#" +
+ *                 postSettingAction.getName() +
+ *                 
(String)Arrays.stream(postSettingAction.getParameterTypes()).map(Class::getSimpleName).collect(Collectors.joining());
+ *             this._postSettingHashcode = 
this._postSettingSignature.hashCode();
+ *         }
+ *     }
+ *
+ *     public void set(ConfiguredObject configuredObject, Object value) throws 
IllegalAccessException, InvocationTargetException
+ *     {
+ *         if (this._preSettingAction != null)
+ *         {
+ *             switch (_preSettingHashcode)
+ *             {
+ *                 case 1:
+ *                    MH_1.invokeExact(configuredObject);
+ *                    break;
+ *                 case 2:
+ *                    MH_2.invokeExact(configuredObject);
+ *                    break;
+ *                 case 3:
+ *                    MH_3.invokeExact(configuredObject);
+ *                    break;
+ *             }
+ *
+ *             this._field.set(configuredObject, value);
+ *
+ *             if (this._postSettingAction != null)
+ *             {
+ *                switch (_postSettingHashcode)
+ *                {
+ *                    case 4:
+ *                       MH_4.invokeExact(configuredObject);
+ *                       break;
+ *                    case 5:
+ *                       MH_5.invokeExact(configuredObject);
+ *                       break;
+ *                    case 6:
+ *                       MH_6.invokeExact(configuredObject);
+ *                       break;
+ *                }
+ *         }
+ *     }
+ * }
+ * </pre>
+ *
+ * @param <T> Class member
+ */
+// sonar complains about underscores in variable names
+@SuppressWarnings("java:S116")
+public class ConfiguredObjectTypeRegistryTransformer<T extends 
AutomatedFieldDescription>
+        extends AbstractQpidTransformer<T>
+        implements QpidTransformer<T>
+{
+    /** Target class name */
+    private static final String CLASS_NAME = 
"org.apache.qpid.server.model.ConfiguredObjectTypeRegistry$AutomatedField";
+
+    /** Internal class name */
+    private static final String INTERNAL_CLASS_NAME =
+            
"org/apache/qpid/server/model/ConfiguredObjectTypeRegistry$AutomatedField";
+
+    /** Method descriptors */
+    private static final String DESC_CONSTRUCTOR =
+            String.format("(%s%s%s)V", referenceName(FIELD), 
referenceName(METHOD), referenceName(METHOD));
+
+    /** Field names */
+    private static final String PRESETTING_ACTION = "_preSettingAction";
+    private static final String POSTSETTING_ACTION = "_postSettingAction";
+    private static final String PRESETTING_SIGNATURE = "_preSettingSignature";
+    private static final String POSTSETTING_SIGNATURE = 
"_postSettingSignature";
+    private static final String PRESETTING_HASHCODE = "_preSettingHashcode";
+    private static final String POSTSETTING_HASHCODE = "_postSettingHashcode";
+
+    /** Logger */
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ConfiguredObjectTypeRegistryTransformer.class);
+
+    /** List of fields @AutomatedField */
+    private final List<T> _fields;
+
+    /**
+     * Constructor stores method list
+     *
+     * @param fields Automated fields to be handled
+     */
+    public ConfiguredObjectTypeRegistryTransformer(final List<T> fields)
+    {
+        super();
+        _fields = new ArrayList<>(fields);
+    }
+
+    @Override
+    public Logger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    public List<T> getMemberDescriptions()
+    {
+        return new ArrayList<>(_fields);
+    }
+
+    @Override
+    public ClassVisitor getTransformer(final ClassVisitor cv)
+    {
+        return new Transformer(cv, getMemberDescriptions());
+    }
+
+    private class Transformer extends ClassVisitor
+    {
+        private final List<MethodDescription> _beforeSetMethods;
+        private final List<MethodDescription> _afterSetMethods;
+        private final List<MethodDescription> _allMethods;
+
+        private Transformer(final ClassVisitor cv, final List<T> fields)
+        {
+            super(Opcodes.ASM9, cv);
+            _beforeSetMethods = 
fields.stream().map(AutomatedFieldDescription::getBeforeSet)
+                                      .filter(Objects::nonNull).distinct()
+                                      .collect(Collectors.toList());
+            _afterSetMethods = 
fields.stream().map(AutomatedFieldDescription::getAfterSet)
+                                     .filter(Objects::nonNull).distinct()
+                                     .collect(Collectors.toList());
+            _allMethods = Stream.concat(_beforeSetMethods.stream(), 
_afterSetMethods.stream())
+                                .collect(Collectors.toList());
+        }
+
+        @Override()
+        public void visitEnd()
+        {
+            final int size = _allMethods.size();
+            visitMethodHandleFields(size, (ClassWriter) cv);
+            final int access = Opcodes.ACC_PRIVATE;
+            visitField(access, PRESETTING_SIGNATURE, referenceName(STRING), 
null, null).visitEnd();
+            visitField(access, POSTSETTING_SIGNATURE, referenceName(STRING), 
null, null).visitEnd();
+            visitField(access, PRESETTING_HASHCODE, TYPE_INT, null, 
null).visitEnd();
+            visitField(access, POSTSETTING_HASHCODE, TYPE_INT, null, 
null).visitEnd();
+            visitStaticInitializer(_allMethods, INTERNAL_CLASS_NAME, 
CLASS_NAME, (ClassWriter) cv);
+        }
+
+        @Override
+        public MethodVisitor visitMethod(final int access,
+                                         final String name,
+                                         final String desc,
+                                         final String signature,
+                                         final String[] exceptions)
+        {
+            MethodVisitor mv = super.visitMethod(access, name, desc, 
signature, exceptions);
+
+            // add custom logic to constructor
+            if (name.equals(INIT) && desc.equals(DESC_CONSTRUCTOR))
+            {
+                mv = new ConstructorTransformer(mv);
+            }
+
+            // add custom logic to set
+            if (SET.equals(name) &&
+                    
"(Lorg/apache/qpid/server/model/ConfiguredObject;Ljava/lang/Object;)V".equals(desc))
+            {
+                mv = new SetTransformer(mv, _beforeSetMethods, 
_afterSetMethods, _allMethods);
+            }
+
+            return mv;
+        }
+    }
+
+    private static class ConstructorTransformer extends MethodVisitor
+    {
+        private ConstructorTransformer(final MethodVisitor delegate)
+        {
+            super(Opcodes.ASM9, delegate);
+        }
+
+        @Override()
+        public void visitInsn(final int opcode)
+        {
+            if (opcode == Opcodes.RETURN)
+            {
+                int argIndex = 2;
+                initSettingAssociatedAction(argIndex, PRESETTING_ACTION, 
PRESETTING_SIGNATURE, PRESETTING_HASHCODE);
+                mv.visitFrame(Opcodes.F_FULL,
+                              4,
+                              new Object[]{INTERNAL_CLASS_NAME, FIELD, 
referenceName(METHOD), referenceName(METHOD)},
+                              0,
+                              new Object[]{});
+
+                argIndex = 3;
+                initSettingAssociatedAction(argIndex, POSTSETTING_ACTION, 
POSTSETTING_SIGNATURE, POSTSETTING_HASHCODE);
+                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+            }
+            super.visitInsn(opcode);
+        }
+
+        private void initSettingAssociatedAction(final int argIndex,
+                                                 final String action,
+                                                 final String signature,
+                                                 final String hashcode)
+        {
+            mv.visitVarInsn(Opcodes.ALOAD, 0);
+            mv.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, action, 
referenceName(METHOD));
+            final Label outerPostSetting = new Label();
+            mv.visitJumpInsn(Opcodes.IFNULL, outerPostSetting);
+            final Label innerPostSetting = new Label();
+            mv.visitLabel(innerPostSetting);
+
+            generateSignature(mv, argIndex, INTERNAL_CLASS_NAME, signature);
+
+            final Label postSetting = new Label();
+            mv.visitLabel(postSetting);
+
+            generateHashCode(mv, INTERNAL_CLASS_NAME, signature, hashcode);
+
+            mv.visitLabel(outerPostSetting);
+        }
+    }
+
+    private class SetTransformer extends MethodVisitor
+    {
+        private final MethodVisitor _target;
+        private final List<MethodDescription> _beforeSetMethods;
+        private final List<MethodDescription> _afterSetMethods;
+        private final List<MethodDescription> _allMethods;
+
+        private SetTransformer(final MethodVisitor delegate,
+                               final List<MethodDescription> beforeSetMethods,
+                               final List<MethodDescription> afterSetMethods,
+                               final List<MethodDescription> allMethods)
+        {
+            super(Opcodes.ASM9, null);
+            _target = delegate;
+            _afterSetMethods = afterSetMethods;
+            _beforeSetMethods = beforeSetMethods;
+            _allMethods = allMethods;
+        }
+
+        @Override
+        public void visitCode()
+        {
+            _target.visitCode();
+
+            visitSettingAssociatedAction(PRESETTING_ACTION,
+                                         _beforeSetMethods,
+                                         PRESETTING_HASHCODE,
+                                         PRESETTING_SIGNATURE);
+
+            _target.visitVarInsn(Opcodes.ALOAD, 0);
+            _target.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
FIELD_FIELD, referenceName(FIELD));
+            _target.visitVarInsn(Opcodes.ALOAD, 1);
+            _target.visitVarInsn(Opcodes.ALOAD, 2);
+            _target.visitMethodInsn(Opcodes.INVOKEVIRTUAL, FIELD, SET, 
DESC_SET, FALSE);
+
+            visitSettingAssociatedAction(POSTSETTING_ACTION,
+                                         _afterSetMethods,
+                                         POSTSETTING_HASHCODE,
+                                         POSTSETTING_SIGNATURE);
+            _target.visitInsn(Opcodes.RETURN);
+
+            _target.visitMaxs(0, 0);
+            _target.visitEnd();
+        }
+
+        private void visitSettingAssociatedAction(final String action,
+                                                  final 
List<MethodDescription> methods,
+                                                  final String hashcode,
+                                                  final String signature)
+        {
+            final Label outer = new Label();
+            _target.visitLabel(outer);
+            _target.visitVarInsn(Opcodes.ALOAD, 0);
+            _target.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
action, referenceName(METHOD));
+            final Label outerPostSetting = new Label();
+            _target.visitJumpInsn(Opcodes.IFNULL, outerPostSetting);
+            final Label innerPostSetting = new Label();
+            _target.visitLabel(innerPostSetting);
+
+            call(_target,
+                 _allMethods,
+                 methods,
+                 hashcode,
+                 signature,
+                 outerPostSetting);
+            _target.visitInsn(Opcodes.POP);
+
+            _target.visitLabel(outerPostSetting);
+            _target.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+        }
+
+        private void call(final MethodVisitor mv,
+                          final List<MethodDescription> allMethods,
+                          final List<MethodDescription> methods,
+                          final String hashcodeVariable,
+                          final String signatureVariable,
+                          final Label label)
+        {
+            final int configuredObjectIndex = 1;
+
+            mv.visitVarInsn(Opcodes.ALOAD, 0);
+            mv.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
hashcodeVariable, TYPE_INT);
+            mv.visitVarInsn(Opcodes.ISTORE, 7);
+            final int signatureIndex = 7;
+
+            final Map<String, AttributeStackAddress> stackMap = 
createAttributesStackMap(methods);
+            final int[] hashes = new int[stackMap.size()];
+            final Label[] switchJumpLabels = new Label[stackMap.size()];
+            fillHashesAndLabels(stackMap, hashes, switchJumpLabels);
+
+            final Label switchLabel = new Label();
+
+            mv.visitVarInsn(Opcodes.ILOAD, signatureIndex);
+            mv.visitLookupSwitchInsn(switchLabel, hashes, switchJumpLabels);
+
+            for (int i = 0; i < allMethods.size(); i++)
+            {
+                final MethodDescription method = allMethods.get(i);
+                if (!methods.contains(method))
+                {
+                    continue;
+                }
+                mv.visitLabel(stackMap.get(method.getSignature()).getLabel());
+                mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+                mv.visitFieldInsn(Opcodes.GETSTATIC, INTERNAL_CLASS_NAME, 
"MH_" + i, referenceName(METHOD_HANDLE));
+                mv.visitVarInsn(Opcodes.ALOAD, configuredObjectIndex);
+                mv.visitTypeInsn(Opcodes.CHECKCAST, 
method.getDeclaringClass());
+                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, METHOD_HANDLE, 
INVOKE_EXACT, sig(method), FALSE);
+                mv.visitJumpInsn(Opcodes.GOTO, label);
+            }
+
+            mv.visitLabel(switchLabel);
+            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+
+            /* create code: throw new UnsupportedOperationException(msg) */
+            mv.visitVarInsn(Opcodes.ALOAD, 0);
+            mv.visitFieldInsn(Opcodes.GETFIELD, INTERNAL_CLASS_NAME, 
signatureVariable, referenceName(STRING));
+            mv.visitVarInsn(Opcodes.ASTORE, 3);
+            mv.visitTypeInsn(Opcodes.NEW, SSRE);
+            mv.visitInsn(Opcodes.DUP);
+            mv.visitLdcInsn("Failed to call automated field callback %s");
+            mv.visitInsn(Opcodes.ICONST_1);
+            mv.visitTypeInsn(Opcodes.ANEWARRAY, OBJECT);
+            mv.visitInsn(Opcodes.DUP);
+            mv.visitInsn(Opcodes.ICONST_0);
+            mv.visitVarInsn(Opcodes.ALOAD, 3);
+            mv.visitInsn(Opcodes.AASTORE);
+            mv.visitMethodInsn(Opcodes.INVOKESTATIC, STRING, FORMAT, 
DESC_FORMAT, FALSE);
+            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, SSRE, INIT, 
DESC_EXCEPTION1, FALSE);
+            mv.visitInsn(Opcodes.ATHROW);
+        }
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/QpidClassFileTransformer.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/QpidClassFileTransformer.java
new file mode 100644
index 0000000000..5389baf3f6
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/QpidClassFileTransformer.java
@@ -0,0 +1,344 @@
+/*
+ * 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.qpid.server.instrumentation.transformer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import 
org.apache.qpid.server.instrumentation.metadata.AutomatedFieldDescription;
+import org.apache.qpid.server.instrumentation.metadata.MethodDescription;
+import org.apache.qpid.server.util.ServerScopedRuntimeException;
+
+/**
+ * Class transformer entry point picking up which classes to transform and 
passing the to delegate transformers
+ */
+// sonar complains about underscores in variable names
+@SuppressWarnings("java:S116")
+public class QpidClassFileTransformer implements ClassFileTransformer
+{
+    /** Qualified class names */
+    private static final String CO_METHOD_ATTRIBUTE_OR_STATISTICS =
+            
"org/apache/qpid/server/model/ConfiguredObjectMethodAttributeOrStatistic";
+    private static final String CO_METHOD_OPERATION = 
"org/apache/qpid/server/model/ConfiguredObjectMethodOperation";
+    private static final String CO_TYPE_REGISTRY =
+            
"org/apache/qpid/server/model/ConfiguredObjectTypeRegistry$AutomatedField";
+
+    /** Mapping between argument names and qualified class names */
+    private static final Map<String, String> TYPES = Map.of(
+            "ConfiguredObjectMethodAttributeOrStatistic", 
CO_METHOD_ATTRIBUTE_OR_STATISTICS,
+            "ConfiguredObjectMethodOperation", CO_METHOD_OPERATION,
+            "AutomatedField", CO_TYPE_REGISTRY);
+
+    /** System classpath property */
+    private static final String CLASSPATH_PROPERTY = "java.class.path";
+
+    /** Apache Qpid root package */
+    private static final String QPID_ROOT_PACKAGE = "org/apache/qpid";
+
+    /** Class file extension */
+    private static final String CLASS_EXTENSION = ".class";
+
+    /**
+     * Annotations responsible for automated fields:
+     * <p>
+     * org.apache.qpid.server.model.ManagedAttributeField
+     */
+    private static final List<String> AUTOMATED_FIELD_ANNOTATIONS =
+            List.of("Lorg/apache/qpid/server/model/ManagedAttributeField;");
+
+    /**
+     * Annotations responsible for attribute getters:
+     * <p>
+     * org.apache.qpid.server.model.DerivedAttribute,
+     * org.apache.qpid.server.model.ManagedAttribute,
+     * org.apache.qpid.server.model.ManagedStatistic
+     */
+    private static final List<String> GETTER_ANNOTATIONS = List.of(
+            "Lorg/apache/qpid/server/model/DerivedAttribute;",
+            "Lorg/apache/qpid/server/model/ManagedAttribute;",
+            "Lorg/apache/qpid/server/model/ManagedStatistic;");
+
+    /**
+     * Annotations responsible for managed operations:
+     * <p>
+     * org.apache.qpid.server.model.ManagedOperation
+     */
+    private static final List<String> OPERATION_ANNOTATIONS =
+            List.of("Lorg/apache/qpid/server/model/ManagedOperation;");
+
+    /**
+     * Annotations responsible for state transitions:
+     * <p>
+     * org.apache.qpid.server.model.StateTransition
+     */
+    private static final List<String> STATE_TRANSITION_ANNOTATIONS =
+            List.of("Lorg/apache/qpid/server/model/StateTransition;");
+
+    /** All annotations involved in class transformation */
+    private static final List<String> ANNOTATIONS = Stream.of(
+            AUTOMATED_FIELD_ANNOTATIONS,
+            GETTER_ANNOTATIONS,
+            OPERATION_ANNOTATIONS,
+            
STATE_TRANSITION_ANNOTATIONS).flatMap(List::stream).collect(Collectors.toUnmodifiableList());
+
+    /** Logger */
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(QpidClassFileTransformer.class);
+
+    /** Class transformers */
+    private final Map<String, QpidTransformer<?>> _transformers = new 
HashMap<>();
+
+    /** Classes allowed to instrument (configured via agent arguments) */
+    private final List<String> _allowedTypes;
+
+    /**
+     * Scans classpath finding methods and fields annotated by annotations of 
interest. Gathers information about
+     * affected methods and fields, passing them to appropriate class 
transformers.
+     *
+     * @param instrumentation Instrumentation instance
+     */
+    public QpidClassFileTransformer(final String arg, final Instrumentation 
instrumentation)
+    {
+        LOGGER.info("Initializing QPID instrumentation agent");
+
+        _allowedTypes = parseArgs(arg);
+
+        // get classpath entries
+        final String classPath = System.getProperty(CLASSPATH_PROPERTY);
+        final List<String> classPathEntries =
+                
Arrays.stream(classPath.split(File.pathSeparator)).collect(Collectors.toList());
+
+        // prepare collections to store metadata
+        final List<AutomatedFieldDescription> automatedFields = new 
ArrayList<>();
+        final List<MethodDescription> getters = new ArrayList<>();
+        final List<MethodDescription> operations = new ArrayList<>();
+        final List<MethodDescription> stateTransitions = new ArrayList<>();
+
+        // iterate over classpath entries and parse class files
+        for (final String classPathEntry : classPathEntries)
+        {
+            try (final JarFile jarFile = jarFile(classPathEntry))
+            {
+                if (jarFile != null)
+                {
+                    jarFile.stream()
+                           .filter(jarEntry ->
+                                
jarEntry.getName().startsWith(QPID_ROOT_PACKAGE) &&
+                                jarEntry.getName().endsWith(CLASS_EXTENSION))
+                           .forEach(jarEntry ->
+                                parse(jarFile, jarEntry, automatedFields, 
getters, operations, stateTransitions));
+                }
+            }
+            catch (IOException e)
+            {
+                LOGGER.error("Error when accessing class files", e);
+            }
+        }
+
+        LOGGER.info("Identified {} automated fields", automatedFields.size());
+        LOGGER.info("Identified {} managed attribute methods", getters.size());
+        LOGGER.info("Identified {} managed operation methods", 
operations.size());
+        LOGGER.info("Identified {} state transition methods", 
stateTransitions.size());
+        LOGGER.info("Loaded {} classes", 
instrumentation.getAllLoadedClasses().length);
+
+        // create class transformers
+        _transformers.put(CO_METHOD_ATTRIBUTE_OR_STATISTICS,
+                          new 
ConfiguredObjectMethodAttributeOrStatisticTransformer<>(getters));
+        _transformers.put(CO_METHOD_OPERATION, new 
ConfiguredObjectMethodOperationTransformer<>(operations));
+        _transformers.put(CO_TYPE_REGISTRY, new 
ConfiguredObjectTypeRegistryTransformer<>(automatedFields));
+
+        LOGGER.info("QPID instrumentation agent initialized");
+    }
+
+    /**
+     * Parses agent arguments
+     *
+     * @param args Agent string argument
+     *
+     * @return List of classes to instrument
+     */
+    private List<String> parseArgs(String args)
+    {
+        if (args == null || args.isEmpty())
+        {
+            return new ArrayList<>(TYPES.values());
+        }
+        return Arrays
+                .stream(args.split(","))
+                .map(String::trim).filter(TYPES::containsKey)
+                .map(TYPES::get).distinct().collect(Collectors.toList());
+    }
+
+    /**
+     * Reads JAR file
+     *
+     * @param path File path
+     * @return JarFile instance
+     */
+    private JarFile jarFile(final String path)
+    {
+        try
+        {
+            return new JarFile(path);
+        }
+        catch (IOException e)
+        {
+            LOGGER.debug("Error when parsing jar file", e);
+            return null;
+        }
+    }
+
+    /**
+     * Parses JAR entry iterating over classes and saving information about 
methods and field of interest into the
+     * lists provided.
+     * <p>
+     * To avoid eager class loading (classes being parsed shouldn't be loaded 
in the instrumentation agent, otherwise
+     * they couldn't be transformed) ASM parser is used instead of reflection.
+     *
+     * @param jar              JAR file
+     * @param jarEntry         JAR file entry
+     * @param automatedFields  List of automated fields
+     * @param getters          List of getter method
+     * @param operations       List of managed operation methods
+     * @param stateTransitions List of state transition methods
+     */
+    private void parse(final JarFile jar,
+                       final JarEntry jarEntry,
+                       final List<AutomatedFieldDescription> automatedFields,
+                       final List<MethodDescription> getters,
+                       final List<MethodDescription> operations,
+                       final List<MethodDescription> stateTransitions)
+    {
+        try (final InputStream inputStream = jar.getInputStream(jarEntry))
+        {
+            final ClassReader classReader = new ClassReader(inputStream);
+            final ClassNode classNode = new ClassNode();
+            classReader.accept(classNode, ClassReader.EXPAND_FRAMES);
+            final List<MethodNode> methods = classNode.methods;
+            final List<FieldNode> fields = classNode.fields;
+
+            methods.stream()
+                   .filter(methodNode -> methodNode.visibleAnnotations != null)
+                   .forEach(methodNode -> parse(classNode, methodNode, 
getters, operations, stateTransitions));
+
+            fields.stream()
+                  .filter(field -> field.visibleAnnotations != null)
+                  .forEach(field -> parse(classNode, field, automatedFields));
+        }
+        catch (IOException e)
+        {
+            throw new ServerScopedRuntimeException(e);
+        }
+    }
+
+    private void parse(final ClassNode classNode,
+                       final MethodNode methodNode,
+                       final List<MethodDescription> getters,
+                       final List<MethodDescription> operations,
+                       final List<MethodDescription> stateTransitions)
+    {
+        final List<AnnotationNode> annotations = methodNode.visibleAnnotations;
+        annotations.stream().filter(node -> ANNOTATIONS.contains(node.desc))
+                   .forEach(node -> parse(classNode, methodNode, node, 
getters, operations, stateTransitions));
+    }
+
+    private void parse(final ClassNode classNode,
+                       final MethodNode methodNode,
+                       final AnnotationNode node,
+                       final List<MethodDescription> getters,
+                       final List<MethodDescription> operations,
+                       final List<MethodDescription> stateTransitions)
+    {
+        if (GETTER_ANNOTATIONS.contains(node.desc))
+        {
+            getters.add(MethodDescription.of(classNode, methodNode));
+        }
+        if (OPERATION_ANNOTATIONS.contains(node.desc))
+        {
+            operations.add(MethodDescription.of(classNode, methodNode));
+        }
+        if (STATE_TRANSITION_ANNOTATIONS.contains(node.desc))
+        {
+            stateTransitions.add(MethodDescription.of(classNode, methodNode));
+        }
+    }
+
+    private void parse(final ClassNode classNode,
+                       final FieldNode fieldNode,
+                       final List<AutomatedFieldDescription> automatedFields)
+    {
+        final List<AnnotationNode> annotations = fieldNode.visibleAnnotations;
+        annotations.stream()
+                   .filter(node -> 
AUTOMATED_FIELD_ANNOTATIONS.contains(node.desc))
+                   .forEach(node -> 
automatedFields.add(AutomatedFieldDescription.of(classNode, fieldNode, node)));
+    }
+
+    /**
+     * Transforms original class
+     *
+     * @param loader              the defining loader of the class to be 
transformed,
+     *                            may be {@code null} if the bootstrap loader
+     * @param className           the name of the class in the internal form 
of fully
+     *                            qualified class and interface names as 
defined in
+     *                            <i>The Java Virtual Machine 
Specification</i>.
+     *                            For example, <code>"java/util/List"</code>.
+     * @param classBeingRedefined if this is triggered by a redefine or 
re-transform,
+     *                            the class being redefined or re-transformed;
+     *                            if this is a class load, {@code null}
+     * @param protectionDomain    the protection domain of the class being 
defined or redefined
+     * @param classfileBuffer     the input byte buffer in class file format - 
must not be modified
+     * @return Transformed class in form of a byte array
+     */
+    public byte[] transform(final ClassLoader loader,
+                            final String className,
+                            final Class<?> classBeingRedefined,
+                            final ProtectionDomain protectionDomain,
+                            final byte[] classfileBuffer)
+    {
+        if (_allowedTypes.contains(className) && 
_transformers.containsKey(className))
+        {
+            LOGGER.info("Transforming {}", className);
+            final byte[] bytes = 
_transformers.get(className).generate(classfileBuffer);
+            LOGGER.info("{} transformed", className);
+            return bytes;
+        }
+        return classfileBuffer;
+    }
+}
diff --git 
a/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/QpidTransformer.java
 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/QpidTransformer.java
new file mode 100644
index 0000000000..6d49258750
--- /dev/null
+++ 
b/broker-instrumentation/src/main/java/org/apache/qpid/server/instrumentation/transformer/QpidTransformer.java
@@ -0,0 +1,65 @@
+/*
+ * 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.qpid.server.instrumentation.transformer;
+
+import java.util.List;
+
+import org.objectweb.asm.ClassVisitor;
+import org.slf4j.Logger;
+
+import org.apache.qpid.server.instrumentation.metadata.MemberDescription;
+
+/**
+ * Class transformer
+ *
+ * @param <T> Class member
+ */
+public interface QpidTransformer<T extends MemberDescription>
+{
+    /**
+     * Transforms class
+     *
+     * @param bytes Original class in form of a byte array
+     * @return Transformed class in form of a byte array
+     */
+    byte[] generate(byte[] bytes);
+
+    /**
+     * Returns list of class members to be instrumented
+     *
+     * @return List of class members to be instrumented
+     */
+    List<T> getMemberDescriptions();
+
+    /**
+     * Returns delegate transformer
+     *
+     * @param cv ClassVisitor instance
+     *
+     * @return ClassVisitor instance
+     */
+    ClassVisitor getTransformer(final ClassVisitor cv);
+
+    /**
+     * Returns logger instance
+     *
+     * @return Logger instance
+     */
+    Logger getLogger();
+}
diff --git 
a/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractConnectionLimitProvider.java
 
b/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractConnectionLimitProvider.java
index 22e0367e1c..15a6005c85 100644
--- 
a/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractConnectionLimitProvider.java
+++ 
b/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractConnectionLimitProvider.java
@@ -38,7 +38,7 @@ import 
org.apache.qpid.server.security.limit.ConnectionLimiter;
 import org.apache.qpid.server.user.connection.limits.config.RuleSetCreator;
 import org.apache.qpid.server.util.urlstreamhandler.data.Handler;
 
-abstract class AbstractConnectionLimitProvider<X extends 
AbstractConnectionLimitProvider<X>>
+public abstract class AbstractConnectionLimitProvider<X extends 
AbstractConnectionLimitProvider<X>>
         extends AbstractConfiguredObject<X> implements 
ConnectionLimitProvider<X>
 {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(AbstractConnectionLimitProvider.class);
diff --git 
a/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractFileBasedConnectionLimitProvider.java
 
b/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractFileBasedConnectionLimitProvider.java
index 07d2c9bc66..c58fbb3299 100644
--- 
a/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractFileBasedConnectionLimitProvider.java
+++ 
b/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractFileBasedConnectionLimitProvider.java
@@ -33,7 +33,7 @@ import 
org.apache.qpid.server.user.connection.limits.config.FileParser;
 import org.apache.qpid.server.user.connection.limits.config.Rule;
 import org.apache.qpid.server.user.connection.limits.config.RuleSetCreator;
 
-abstract class AbstractFileBasedConnectionLimitProvider<C extends 
AbstractFileBasedConnectionLimitProvider<C>>
+public abstract class AbstractFileBasedConnectionLimitProvider<C extends 
AbstractFileBasedConnectionLimitProvider<C>>
         extends AbstractConnectionLimitProvider<C> implements 
FileBasedConnectionLimitProvider<C>
 {
     static final String FILE_PROVIDER_TYPE = "ConnectionLimitFile";
diff --git 
a/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractRuleBasedConnectionLimitProvider.java
 
b/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractRuleBasedConnectionLimitProvider.java
index 4ff622f256..e6735118a1 100644
--- 
a/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractRuleBasedConnectionLimitProvider.java
+++ 
b/broker-plugins/connection-limits/src/main/java/org/apache/qpid/server/user/connection/limits/plugins/AbstractRuleBasedConnectionLimitProvider.java
@@ -45,7 +45,7 @@ import 
org.apache.qpid.server.user.connection.limits.config.Rule;
 import 
org.apache.qpid.server.user.connection.limits.config.RulePredicates.Property;
 import org.apache.qpid.server.user.connection.limits.config.RuleSetCreator;
 
-abstract class AbstractRuleBasedConnectionLimitProvider<C extends 
AbstractRuleBasedConnectionLimitProvider<C>>
+public abstract class AbstractRuleBasedConnectionLimitProvider<C extends 
AbstractRuleBasedConnectionLimitProvider<C>>
         extends AbstractConnectionLimitProvider<C> implements 
RuleBasedConnectionLimitProvider<C>
 {
     private static final String RULES = "rules";
diff --git a/doc/java-broker/src/docbkx/Java-Broker-Runtime.xml 
b/doc/java-broker/src/docbkx/Java-Broker-Runtime.xml
index 1f56d61c17..7b2fa20d04 100644
--- a/doc/java-broker/src/docbkx/Java-Broker-Runtime.xml
+++ b/doc/java-broker/src/docbkx/Java-Broker-Runtime.xml
@@ -33,4 +33,5 @@
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; 
href="runtime/Java-Broker-Runtime-Message-Compression.xml"/>
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; 
href="runtime/Java-Broker-Runtime-Connection-Limit.xml"/>
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; 
href="runtime/Java-Broker-Runtime-Memory.xml"/>
+  <xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; 
href="runtime/Java-Broker-Runtime-Instrumentation.xml"/>
 </chapter>
diff --git 
a/doc/java-broker/src/docbkx/runtime/Java-Broker-Runtime-Instrumentation.xml 
b/doc/java-broker/src/docbkx/runtime/Java-Broker-Runtime-Instrumentation.xml
new file mode 100644
index 0000000000..1dc1452fa1
--- /dev/null
+++ b/doc/java-broker/src/docbkx/runtime/Java-Broker-Runtime-Instrumentation.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!--
+
+ 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.
+
+-->
+
+<section xmlns="http://docbook.org/ns/docbook"; version="5.0" 
xml:id="Java-Broker-Runtime-Instrumentation">
+    <title>Broker Instrumentation</title>
+    <para>The Apache Qpid Broker-J heavy relies on java reflection mechanism. 
A static instrumentation agent
+        can be used to replace method.invoke() reflection calls with static 
final MethodHandle.invokeExact().</para>
+    <para>To use instrumentation agent following JVM argument should be added 
to the broker start
+        parameters:</para>
+    <para>-javaagent:$BROKER_DIR/lib/broker-instrumentation.jar</para>
+
+</section>
diff --git a/pom.xml b/pom.xml
index 7748ec0401..024a72ebc1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,6 +117,7 @@
 
     <geronimo-jms-1-1-version>1.1.1</geronimo-jms-1-1-version>
     <geronimo-jms-2-0-version>1.0-alpha-2</geronimo-jms-2-0-version>
+    <asm-version>9.3</asm-version>
 
     <velocity-version>1.4</velocity-version>
     <csvjdbc-version>1.0.35</csvjdbc-version>
@@ -217,6 +218,7 @@
     <module>tck</module>
 
     <module>doc</module>
+    <module>broker-instrumentation</module>
   </modules>
 
   <dependencies>
@@ -620,6 +622,16 @@
           </exclusion>
         </exclusions>
       </dependency>
+      <dependency>
+        <groupId>org.ow2.asm</groupId>
+        <artifactId>asm</artifactId>
+        <version>${asm-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.ow2.asm</groupId>
+        <artifactId>asm-tree</artifactId>
+        <version>${asm-version}</version>
+      </dependency>
       <dependency>
         <groupId>velocity</groupId>
         <artifactId>velocity</artifactId>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to