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]