diff --git a/instrumentation/jsp-4/README.md b/instrumentation/jsp-4/README.md
new file mode 100644
index 0000000000..c70175e7b8
--- /dev/null
+++ b/instrumentation/jsp-4/README.md
@@ -0,0 +1,99 @@
+# jsp-4 Instrumentation Module
+
+## Injection of the Real User Monitoring Script Via JSP Tag Libraries
+Prior to the additions to this instrumentation module, the only way to inject the RUM script into JSPs was during the
+compilation phase of the Jasper compiler, which injects the script into the HTML `
` element, if present in the page
+source.
+
+Some applications use custom JSP tag libraries to create the head tag (and other HTML page elements). In this scenario, the
+RUM script will not be injected because of the way the Jasper compiler instrumentation detects the head tag. This
+instrumentation module weaves the `SimpleTagSupport` and `TagSupport` classes to detect the creation of head elements via the
+tag execution and inject the RUM script at that time.
+
+### Configuration
+To enable tag library instrumentation, both of the following settings must be `true`:
+```
+ browser_monitoring:
+ auto_instrument: true
+ tag_lib_instrument: true
+```
+
+By default, the `tag_lib_instrument` setting is `false`.
+
+The instrumentation will use the regular expression pattern of `` to detect the start of HTML head elements.
+If a tag library emits a more complex head start element, the regular expression can be modified via the `tag_lib_head_pattern`
+config setting. For example:
+```
+ browser_monitoring:
+ tag_lib_head_pattern: ''
+```
+The regular expression will be compiled to be case-insensitive. If the defined regular expression is invalid it will default to ``.
+
+
+### Requirements for Script Injection
+The following are the requirements for the RUM script to be injected from instrumented tag libraries:
+- The application must utilize version 4 of the JSP libraries (jakarta namespace)
+- Only the first instance of a `` string emitted by a tag library will be considered, regardless of context (in a comment, for example)
+- The custom tag must extend either the [SimpleTagSupport](https://jakarta.ee/specifications/pages/3.0/apidocs/jakarta/servlet/jsp/tagext/simpletagsupport) or
+[TagSupport](https://jakarta.ee/specifications/pages/3.0/apidocs/jakarta/servlet/jsp/tagext/tagsupport) classes
+- The `` element must be emitted from the tag class via the `print(String s)` or `println(String s)` methods of the JspWriter
+fetched from a [JspContext.getOut()](https://jakarta.ee/specifications/pages/3.0/apidocs/jakarta/servlet/jsp/jspcontext) call
+- The head start tag (``) must be totally emitted in a single `print`/`println` call. For example, these are all valid:
+```java
+ out.println("");
+ out.print("");
+ out.println("Test Site");
+ out.println("");
+ out.println("")
+```
+
+### Example SimpleTagSupport Use Case
+##### Tag Library Class
+```java
+ // Package/imports omitted
+ // Also assumes the existence of a valid, corresponding tld file
+ public class SimpleTagExample extends SimpleTagSupport {
+ public void doTag() throws JspException, IOException {
+ JspWriter out = getJspContext().getOut();
+ out.println("\n" +
+ "\n" +
+ "Test Site" +
+ "");
+ }
+ }
+```
+
+##### JSP
+```html
+<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
+<%@ taglib prefix="simple" uri="http://com.sun.simpletagexample"%>
+
+
+
+
+
+ Sample JSP
+
+
+```
+
+In this scenario, the `SimpleTagSupport` instrumentation will detect the output of the head tag and inject the RUM script
+just like the Jasper compiler instrumentation, which will result in the following HTML:
+```html
+
+
+
+
+
+
+Test Site
+
+
+ Sample JSP
+
+
+```
+
+### Example TagSupport Use Case
+Tag libraries that extend the `TagSupport` class work in largely the same way as the `SimpleTagSupport` based tag libraries. The instrumentation
+will detect head element generation via the `doStartTag` or `doEndTag` method calls and will inject the RUM script appropriately.
diff --git a/instrumentation/jsp-4/build.gradle b/instrumentation/jsp-4/build.gradle
new file mode 100644
index 0000000000..77e52d5dc8
--- /dev/null
+++ b/instrumentation/jsp-4/build.gradle
@@ -0,0 +1,30 @@
+
+dependencies {
+ implementation(project(":agent-bridge"))
+ implementation("jakarta.servlet.jsp:jakarta.servlet.jsp-api:4.0.0")
+ implementation("jakarta.servlet:jakarta.servlet-api:6.1.0")
+ implementation("jakarta.el:jakarta.el-api:6.0.0")
+}
+
+jar {
+ manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.jsp-4' }
+}
+
+verifyInstrumentation {
+ passesOnly('jakarta.servlet.jsp:jakarta.servlet.jsp-api:[4.0.0,)') {
+ implementation("jakarta.el:jakarta.el-api:6.0.0")
+ implementation("jakarta.servlet:jakarta.servlet-api:6.1.0")
+ }
+}
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+site {
+ title 'JSP'
+ type 'Other'
+ versionOverride '[4.0,)'
+}
diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspContextWrapper.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspContextWrapper.java
new file mode 100644
index 0000000000..f8d866e14c
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspContextWrapper.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * * Copyright 2024 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package com.nr.agent.instrumentation.jsp4;
+
+import jakarta.servlet.jsp.JspContext;
+import jakarta.servlet.jsp.JspWriter;
+import java.util.Enumeration;
+
+public class JspContextWrapper extends JspContext {
+ private final JspContext wrappedContext;
+ private final JspWriter wrappedWriter;
+
+ public JspContextWrapper(JspContext originalContext, JspWriter wrappedWriter) {
+ this.wrappedContext = originalContext;
+ this.wrappedWriter = wrappedWriter;
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ wrappedContext.setAttribute(name, value);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value, int scope) {
+ wrappedContext.setAttribute(name, value, scope);
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return wrappedContext.getAttribute(name);
+ }
+
+ @Override
+ public Object getAttribute(String name, int scope) {
+ return wrappedContext.getAttribute(name, scope);
+ }
+
+ @Override
+ public Object findAttribute(String name) {
+ return wrappedContext.findAttribute(name);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ wrappedContext.removeAttribute(name);
+ }
+
+ @Override
+ public void removeAttribute(String name, int scope) {
+ wrappedContext.removeAttribute(name, scope);
+ }
+
+ @Override
+ public int getAttributesScope(String name) {
+ return wrappedContext.getAttributesScope(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNamesInScope(int scope) {
+ return wrappedContext.getAttributeNamesInScope(scope);
+ }
+
+ @Override
+ public JspWriter getOut() {
+ return wrappedWriter;
+ }
+
+ @Override
+ public jakarta.el.ELContext getELContext() {
+ return wrappedContext.getELContext();
+ }
+}
diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspUtils.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspUtils.java
new file mode 100644
index 0000000000..5d09d2c9be
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspUtils.java
@@ -0,0 +1,76 @@
+/*
+ *
+ * * Copyright 2022 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package com.nr.agent.instrumentation.jsp4;
+
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import com.newrelic.agent.bridge.AgentBridge;
+import com.newrelic.agent.bridge.TracedMethod;
+import com.newrelic.agent.bridge.TransactionNamePriority;
+import com.newrelic.api.agent.NewRelic;
+
+public class JspUtils {
+ public static final String JSP_CATEGORY = "JSP";
+ public static final String ORG_APACHE_JSP = "org.apache.jsp.";
+ public static final Pattern JSP_PATTERN = Pattern.compile("_jsp$");
+ public static final Pattern WEB_INF_PATTERN = Pattern.compile("WEB_002dINF");
+ private static final String AUTO_INSTRUMENT_ENABLED_CONFIG = "browser_monitoring.auto_instrument";
+ private static final String TAG_LIB_ENABLED_CONFIG = "browser_monitoring.tag_lib_instrument";
+ private static final String TAG_LIB_HEAD_REGEX = "browser_monitoring.tag_lib_head_pattern";
+ private static final String TAG_LIB_HEAD_REGEX_DEFAULT = "";
+
+ public static final Pattern START_HEAD_REGEX = generateStartHeadElementRegExPattern();
+
+ public static void setTransactionName(Class> jspClass, TracedMethod timedMethod) {
+ String name = jspClass.getName();
+ try {
+ if (name.startsWith(ORG_APACHE_JSP)) {
+ name = name.substring(ORG_APACHE_JSP.length()).replace('.', '/');
+ name = WEB_INF_PATTERN.matcher(name).replaceFirst("WEB-INF");
+ } else {
+ int index = name.lastIndexOf('.');
+ if (index > 0) {
+ name = name.substring(index + 1);
+ }
+ }
+ name = JSP_PATTERN.matcher(name).replaceAll(".jsp");
+ AgentBridge.getAgent().getTransaction().setTransactionName(TransactionNamePriority.JSP, false,
+ JSP_CATEGORY, name);
+
+ timedMethod.setMetricName("Jsp", name);
+ } catch (Exception e) {
+ NewRelic.getAgent().getLogger().log(Level.FINER, "An error occurred formatting a jsp name : {0}", e);
+ }
+ }
+
+ public static boolean isTagLibInstrumentationEnabled() {
+ return NewRelic.getAgent().getConfig().getValue(AUTO_INSTRUMENT_ENABLED_CONFIG, Boolean.FALSE) &&
+ NewRelic.getAgent().getConfig().getValue(TAG_LIB_ENABLED_CONFIG, Boolean.FALSE);
+ }
+
+ private static Pattern generateStartHeadElementRegExPattern() {
+ String regexString = NewRelic.getAgent().getConfig().getValue(TAG_LIB_HEAD_REGEX, TAG_LIB_HEAD_REGEX_DEFAULT);
+ Pattern pattern;
+
+ try {
+ pattern = Pattern.compile(regexString, Pattern.CASE_INSENSITIVE + Pattern.MULTILINE);
+ } catch (PatternSyntaxException e) {
+ NewRelic.getAgent().getLogger().log(Level.WARNING, "Invalid pattern defined for tag lib start head regex: {0} Defaulting to: {1}",
+ regexString, TAG_LIB_HEAD_REGEX_DEFAULT);
+ pattern = Pattern.compile(TAG_LIB_HEAD_REGEX_DEFAULT, Pattern.CASE_INSENSITIVE + Pattern.MULTILINE);
+ }
+
+ if (NewRelic.getAgent().getLogger().isLoggable(Level.FINEST)) {
+ NewRelic.getAgent().getLogger().log(Level.FINEST, "Tag lib start head regex to be used for RUM script injection: {0}", pattern.pattern());
+ }
+
+ return pattern;
+ }
+}
diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspWriterWrapper.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspWriterWrapper.java
new file mode 100644
index 0000000000..dea733a98f
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/JspWriterWrapper.java
@@ -0,0 +1,189 @@
+/*
+ *
+ * * Copyright 2024 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package com.nr.agent.instrumentation.jsp4;
+
+import com.newrelic.api.agent.NewRelic;
+
+import jakarta.servlet.jsp.JspWriter;
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+public class JspWriterWrapper extends JspWriter {
+ private JspWriter originalWriter;
+
+ private boolean found = false;
+
+ public JspWriterWrapper(JspWriter originalWriter) {
+ this(-1, false);
+ this.originalWriter = originalWriter;
+ }
+
+ protected JspWriterWrapper(int bufferSize, boolean autoFlush) {
+ super(bufferSize, autoFlush);
+ }
+
+ @Override
+ public void newLine() throws IOException {
+ originalWriter.newLine();
+ }
+
+ @Override
+ public void print(boolean b) throws IOException {
+ originalWriter.print(b);
+ }
+
+ @Override
+ public void print(char c) throws IOException {
+ originalWriter.print(c);
+ }
+
+ @Override
+ public void print(int i) throws IOException {
+ originalWriter.print(i);
+ }
+
+ @Override
+ public void print(long l) throws IOException {
+ originalWriter.print(l);
+ }
+
+ @Override
+ public void print(float f) throws IOException {
+ originalWriter.print(f);
+ }
+
+ @Override
+ public void print(double d) throws IOException {
+ originalWriter.print(d);
+ }
+
+ @Override
+ public void print(char[] s) throws IOException {
+ originalWriter.print(s);
+ }
+
+ @Override
+ public void print(String str) throws IOException {
+ if (!found) {
+ originalWriter.print(detectAndModifyHeadElement(str));
+ } else {
+ originalWriter.print(str);
+ }
+ }
+
+ @Override
+ public void print(Object obj) throws IOException {
+ originalWriter.print(obj);
+ }
+
+ @Override
+ public void println() throws IOException {
+ originalWriter.println();
+ }
+
+ @Override
+ public void println(boolean b) throws IOException {
+ originalWriter.println(b);
+ }
+
+ @Override
+ public void println(char c) throws IOException {
+ originalWriter.println(c);
+ }
+
+ @Override
+ public void println(int i) throws IOException {
+ originalWriter.println(i);
+ }
+
+ @Override
+ public void println(long l) throws IOException {
+ originalWriter.println(l);
+ }
+
+ @Override
+ public void println(float f) throws IOException {
+ originalWriter.println(f);
+ }
+
+ @Override
+ public void println(double d) throws IOException {
+ originalWriter.println(d);
+ }
+
+ @Override
+ public void println(char[] c) throws IOException {
+ originalWriter.println(c);
+ }
+
+ @Override
+ public void println(String str) throws IOException {
+ if (!found) {
+ originalWriter.println(detectAndModifyHeadElement(str));
+ } else {
+ originalWriter.println(str);
+ }
+ }
+
+ @Override
+ public void println(Object o) throws IOException {
+ originalWriter.println(o);
+ }
+
+ @Override
+ public void clear() throws IOException {
+ originalWriter.clear();
+ }
+
+ @Override
+ public void clearBuffer() throws IOException {
+ originalWriter.clearBuffer();
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ originalWriter.write(c);
+ }
+
+ @Override
+ public void write(String s, int off, int len) throws IOException {
+ originalWriter.write(s, off, len);
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ originalWriter.write(cbuf, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ originalWriter.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ originalWriter.close();
+ }
+
+ @Override
+ public int getRemaining() {
+ return originalWriter.getRemaining();
+ }
+
+ private String detectAndModifyHeadElement(String originalContent) {
+ String modifiedContent = null;
+ Matcher m = JspUtils.START_HEAD_REGEX.matcher(originalContent);
+ if (m.find()) {
+ // The getBrowserTimingHeader call performs the "does transaction exist" check so not duplicating that here
+ modifiedContent = originalContent.substring(0, m.end()) + NewRelic.getBrowserTimingHeader() + originalContent.substring(m.end());
+ found = true;
+ }
+
+ return modifiedContent != null ? modifiedContent : originalContent;
+ }
+}
diff --git a/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/PageContextWrapper.java b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/PageContextWrapper.java
new file mode 100644
index 0000000000..075e2ee9f0
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/com/nr/agent/instrumentation/jsp4/PageContextWrapper.java
@@ -0,0 +1,156 @@
+/*
+ *
+ * * Copyright 2024 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package com.nr.agent.instrumentation.jsp4;
+
+import jakarta.servlet.Servlet;
+import jakarta.servlet.ServletConfig;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpSession;
+import jakarta.servlet.jsp.JspWriter;
+import jakarta.servlet.jsp.PageContext;
+import java.io.IOException;
+import java.util.Enumeration;
+
+public class PageContextWrapper extends PageContext {
+ private final PageContext wrappedContext;
+ private final JspWriter wrappedWriter;
+
+ public PageContextWrapper(PageContext wrappedContext, JspWriter wrappedWriter) {
+ this.wrappedContext = wrappedContext;
+ this.wrappedWriter = wrappedWriter;
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ wrappedContext.setAttribute(name, value);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value, int scope) {
+ wrappedContext.setAttribute(name, value, scope);
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return wrappedContext.getAttribute(name);
+ }
+
+ @Override
+ public Object getAttribute(String name, int scope) {
+ return wrappedContext.getAttribute(name, scope);
+ }
+
+ @Override
+ public Object findAttribute(String name) {
+ return wrappedContext.findAttribute(name);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ wrappedContext.removeAttribute(name);
+ }
+
+ @Override
+ public void removeAttribute(String name, int scope) {
+ wrappedContext.removeAttribute(name, scope);
+ }
+
+ @Override
+ public int getAttributesScope(String name) {
+ return wrappedContext.getAttributesScope(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNamesInScope(int scope) {
+ return wrappedContext.getAttributeNamesInScope(scope);
+ }
+
+ @Override
+ public JspWriter getOut() {
+ return wrappedWriter;
+ }
+
+ @Override
+ public jakarta.el.ELContext getELContext() {
+ return wrappedContext.getELContext();
+ }
+
+ @Override
+ public void initialize(Servlet servlet, ServletRequest request, ServletResponse response, String errorPageURL, boolean needsSession, int bufferSize,
+ boolean autoFlush) throws IOException, IllegalStateException, IllegalArgumentException {
+
+ }
+
+ @Override
+ public void release() {
+ wrappedContext.release();
+ }
+
+ @Override
+ public HttpSession getSession() {
+ return wrappedContext.getSession();
+ }
+
+ @Override
+ public Object getPage() {
+ return wrappedContext.getPage();
+ }
+
+ @Override
+ public ServletRequest getRequest() {
+ return wrappedContext.getRequest();
+ }
+
+ @Override
+ public ServletResponse getResponse() {
+ return wrappedContext.getResponse();
+ }
+
+ @Override
+ public Exception getException() {
+ return wrappedContext.getException();
+ }
+
+ @Override
+ public ServletConfig getServletConfig() {
+ return wrappedContext.getServletConfig();
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return wrappedContext.getServletContext();
+ }
+
+ @Override
+ public void forward(String relativeUrlPath) throws ServletException, IOException {
+ wrappedContext.forward(relativeUrlPath);
+ }
+
+ @Override
+ public void include(String relativeUrlPath) throws ServletException, IOException {
+ wrappedContext.include(relativeUrlPath);
+ }
+
+ @Override
+ public void include(String relativeUrlPath, boolean flush) throws ServletException, IOException {
+ wrappedContext.include(relativeUrlPath, flush);
+ }
+
+ @Override
+ public void handlePageException(Exception e) throws ServletException, IOException {
+ wrappedContext.handlePageException(e);
+ }
+
+ @Override
+ public void handlePageException(Throwable t) throws ServletException, IOException {
+ wrappedContext.handlePageException(t);
+ }
+}
diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/HttpJspPage.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/HttpJspPage.java
new file mode 100644
index 0000000000..8ca4bb7145
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/HttpJspPage.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * * Copyright 2022 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package jakarta.servlet.jsp;
+
+import com.newrelic.agent.bridge.AgentBridge;
+import com.newrelic.api.agent.Trace;
+import com.newrelic.api.agent.weaver.MatchType;
+import com.newrelic.api.agent.weaver.Weave;
+import com.newrelic.api.agent.weaver.Weaver;
+import com.nr.agent.instrumentation.jsp4.JspUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@Weave(type = MatchType.Interface)
+public class HttpJspPage {
+
+ @Trace
+ public void _jspService(HttpServletRequest request, HttpServletResponse response) {
+ JspUtils.setTransactionName(getClass(), AgentBridge.getAgent().getTracedMethod());
+
+ Weaver.callOriginal();
+
+ Object exception = request.getAttribute("jakarta.servlet.jsp.jspException");
+ if (exception instanceof Throwable) {
+ AgentBridge.privateApi.reportException((Throwable) exception);
+ }
+ }
+}
diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/el/VariableResolver.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/el/VariableResolver.java
new file mode 100644
index 0000000000..92baefab9b
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/el/VariableResolver.java
@@ -0,0 +1,16 @@
+/*
+ *
+ * * Copyright 2024 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+package jakarta.servlet.jsp.el;
+
+import com.newrelic.api.agent.weaver.SkipIfPresent;
+
+/**
+ * Prevent the jsp-4 module from applying if this interface is present.
+ */
+@SkipIfPresent
+public interface VariableResolver {
+}
diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/SimpleTagSupport_Instrumentation.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/SimpleTagSupport_Instrumentation.java
new file mode 100644
index 0000000000..0f09f41fc7
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/SimpleTagSupport_Instrumentation.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * * Copyright 2024 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package jakarta.servlet.jsp.tagext;
+
+import com.newrelic.api.agent.weaver.MatchType;
+import com.newrelic.api.agent.weaver.Weave;
+import com.nr.agent.instrumentation.jsp4.JspContextWrapper;
+import com.nr.agent.instrumentation.jsp4.JspUtils;
+import com.nr.agent.instrumentation.jsp4.JspWriterWrapper;
+
+import jakarta.servlet.jsp.JspContext;
+
+@Weave(type = MatchType.ExactClass, originalName = "jakarta.servlet.jsp.tagext.SimpleTagSupport")
+public class SimpleTagSupport_Instrumentation {
+ private JspContext jspContext;
+
+ public void setJspContext(JspContext jspContext) {
+ if (JspUtils.isTagLibInstrumentationEnabled()) {
+ JspWriterWrapper wrapperWriter = new JspWriterWrapper(jspContext.getOut());
+ this.jspContext = new JspContextWrapper(jspContext, wrapperWriter);
+ } else {
+ this.jspContext = jspContext;
+ }
+ }
+}
diff --git a/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/TagSupport_Instrumentation.java b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/TagSupport_Instrumentation.java
new file mode 100644
index 0000000000..387a18f58a
--- /dev/null
+++ b/instrumentation/jsp-4/src/main/java/jakarta/servlet/jsp/tagext/TagSupport_Instrumentation.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * * Copyright 2024 New Relic Corporation. All rights reserved.
+ * * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package jakarta.servlet.jsp.tagext;
+
+import com.newrelic.api.agent.weaver.MatchType;
+import com.newrelic.api.agent.weaver.Weave;
+import com.nr.agent.instrumentation.jsp4.JspUtils;
+import com.nr.agent.instrumentation.jsp4.JspWriterWrapper;
+import com.nr.agent.instrumentation.jsp4.PageContextWrapper;
+
+import jakarta.servlet.jsp.PageContext;
+
+@Weave(type = MatchType.ExactClass, originalName = "jakarta.servlet.jsp.tagext.TagSupport")
+public class TagSupport_Instrumentation {
+ protected transient PageContext pageContext;
+
+ public void setPageContext(PageContext pageContext) {
+ if (JspUtils.isTagLibInstrumentationEnabled()) {
+ JspWriterWrapper wrapperWriter = new JspWriterWrapper(pageContext.getOut());
+ this.pageContext = new PageContextWrapper(pageContext, wrapperWriter);
+ } else {
+ this.pageContext = pageContext;
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index b3838b6793..a81724e4e0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -224,6 +224,7 @@ include 'instrumentation:jms-1.1'
include 'instrumentation:jms-3'
include 'instrumentation:jsp-2.4'
include 'instrumentation:jsp-3'
+include 'instrumentation:jsp-4'
include 'instrumentation:kafka-clients-config-1.1.0'
include 'instrumentation:kafka-clients-heartbeat-0.10.1.0'
include 'instrumentation:kafka-clients-heartbeat-2.1.0'