diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/MergedMapView.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/MergedMapView.java new file mode 100644 index 00000000000..fa7e33af3d8 --- /dev/null +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/MergedMapView.java @@ -0,0 +1,120 @@ +package datadog.trace.instrumentation.springweb; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A read-only Map view that lazily merges two maps. When both maps contain the same key, the values + * are combined into a {@link PairList}. The actual merge is deferred until the first operation that + * requires it (e.g. entrySet, size), avoiding unnecessary HashMap copies when the map is never + * iterated. + */ +public final class MergedMapView implements Map { + + private final Map primary; + private final Map secondary; + private Map merged; + + public MergedMapView(Map primary, Map secondary) { + this.primary = primary; + this.secondary = secondary; + } + + private Map merged() { + if (merged == null) { + merged = new HashMap<>(primary); + for (Map.Entry e : secondary.entrySet()) { + String key = e.getKey(); + Object curValue = merged.get(key); + if (curValue != null) { + merged.put(key, new PairList(curValue, e.getValue())); + } else { + merged.put(key, e.getValue()); + } + } + } + return merged; + } + + @Override + public Object get(Object key) { + Object v1 = primary.get(key); + Object v2 = secondary.get(key); + if (v1 != null && v2 != null) { + return new PairList(v1, v2); + } + return v1 != null ? v1 : v2; + } + + @Override + public boolean containsKey(Object key) { + return primary.containsKey(key) || secondary.containsKey(key); + } + + @Override + public int size() { + return merged().size(); + } + + @Override + public boolean isEmpty() { + return primary.isEmpty() && secondary.isEmpty(); + } + + @Override + public boolean containsValue(Object value) { + return merged().containsValue(value); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return merged().keySet(); + } + + @Override + public Collection values() { + return merged().values(); + } + + @Override + public Set> entrySet() { + return merged().entrySet(); + } + + @Override + public boolean equals(Object o) { + return merged().equals(o); + } + + @Override + public int hashCode() { + return merged().hashCode(); + } + + @Override + public String toString() { + return merged().toString(); + } +} diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/TemplateAndMatrixVariablesInstrumentation.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/TemplateAndMatrixVariablesInstrumentation.java index 4ad34b452ec..8bab09719ed 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/TemplateAndMatrixVariablesInstrumentation.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/TemplateAndMatrixVariablesInstrumentation.java @@ -25,7 +25,6 @@ import datadog.trace.api.iast.propagation.PropagationModule; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; -import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; @@ -82,7 +81,7 @@ public void methodAdvice(MethodTransformer transformer) { @Override public String[] helperClassNames() { return new String[] { - packageName + ".PairList", + packageName + ".PairList", packageName + ".MergedMapView", }; } @@ -136,23 +135,15 @@ public static void after( // merge the uri template and matrix variables Map map = null; - if (templateVars instanceof Map) { + if (templateVars instanceof Map && !((Map) templateVars).isEmpty()) { map = (Map) templateVars; } - if (matrixVars instanceof Map) { + if (matrixVars instanceof Map && !((Map) matrixVars).isEmpty()) { + Map matrixMap = (Map) matrixVars; if (map != null) { - map = new HashMap<>(map); - for (Map.Entry e : ((Map) matrixVars).entrySet()) { - String key = e.getKey(); - Object curValue = map.get(key); - if (curValue != null) { - map.put(key, new PairList(curValue, e.getValue())); - } else { - map.put(key, e.getValue()); - } - } + map = new MergedMapView(map, matrixMap); } else { - map = (Map) matrixVars; + map = matrixMap; } } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java/datadog/trace/instrumentation/springweb6/TemplateAndMatrixVariablesInstrumentation.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java/datadog/trace/instrumentation/springweb6/TemplateAndMatrixVariablesInstrumentation.java index 912c9d78a4a..0bc58c31f15 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java/datadog/trace/instrumentation/springweb6/TemplateAndMatrixVariablesInstrumentation.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java/datadog/trace/instrumentation/springweb6/TemplateAndMatrixVariablesInstrumentation.java @@ -64,7 +64,7 @@ public void methodAdvice(MethodTransformer transformer) { @Override public String[] helperClassNames() { return new String[] { - packageName + ".PairList", + packageName + ".PairList", packageName + ".MergedMapView", }; } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/HandleMatchAdvice.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/HandleMatchAdvice.java index e299ac0f91c..9ba3472e291 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/HandleMatchAdvice.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/HandleMatchAdvice.java @@ -16,7 +16,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; import net.bytebuddy.asm.Advice; @@ -66,23 +65,15 @@ public static void after( // merge the uri template and matrix variables Map map = null; - if (templateVars instanceof Map) { + if (templateVars instanceof Map && !((Map) templateVars).isEmpty()) { map = (Map) templateVars; } - if (matrixVars instanceof Map) { + if (matrixVars instanceof Map && !((Map) matrixVars).isEmpty()) { + Map matrixMap = (Map) matrixVars; if (map != null) { - map = new HashMap<>(map); - for (Map.Entry e : ((Map) matrixVars).entrySet()) { - String key = e.getKey(); - Object curValue = map.get(key); - if (curValue != null) { - map.put(key, new PairList(curValue, e.getValue())); - } else { - map.put(key, e.getValue()); - } - } + map = new MergedMapView(map, matrixMap); } else { - map = (Map) matrixVars; + map = matrixMap; } } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/MergedMapView.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/MergedMapView.java new file mode 100644 index 00000000000..ecae5452deb --- /dev/null +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/MergedMapView.java @@ -0,0 +1,120 @@ +package datadog.trace.instrumentation.springweb6; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A read-only Map view that lazily merges two maps. When both maps contain the same key, the values + * are combined into a {@link PairList}. The actual merge is deferred until the first operation that + * requires it (e.g. entrySet, size), avoiding unnecessary HashMap copies when the map is never + * iterated. + */ +public final class MergedMapView implements Map { + + private final Map primary; + private final Map secondary; + private Map merged; + + public MergedMapView(Map primary, Map secondary) { + this.primary = primary; + this.secondary = secondary; + } + + private Map merged() { + if (merged == null) { + merged = new HashMap<>(primary); + for (Map.Entry e : secondary.entrySet()) { + String key = e.getKey(); + Object curValue = merged.get(key); + if (curValue != null) { + merged.put(key, new PairList(curValue, e.getValue())); + } else { + merged.put(key, e.getValue()); + } + } + } + return merged; + } + + @Override + public Object get(Object key) { + Object v1 = primary.get(key); + Object v2 = secondary.get(key); + if (v1 != null && v2 != null) { + return new PairList(v1, v2); + } + return v1 != null ? v1 : v2; + } + + @Override + public boolean containsKey(Object key) { + return primary.containsKey(key) || secondary.containsKey(key); + } + + @Override + public int size() { + return merged().size(); + } + + @Override + public boolean isEmpty() { + return primary.isEmpty() && secondary.isEmpty(); + } + + @Override + public boolean containsValue(Object value) { + return merged().containsValue(value); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return merged().keySet(); + } + + @Override + public Collection values() { + return merged().values(); + } + + @Override + public Set> entrySet() { + return merged().entrySet(); + } + + @Override + public boolean equals(Object o) { + return merged().equals(o); + } + + @Override + public int hashCode() { + return merged().hashCode(); + } + + @Override + public String toString() { + return merged().toString(); + } +}