Created
February 5, 2019 12:28
-
-
Save mdindoffer/21d61433e1cd2b3c23c5880c76a11713 to your computer and use it in GitHub Desktop.
Elastic APM servlet filter for Wicket AJAX requests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package eu.dindoffer.wicket.filter; | |
import co.elastic.apm.api.ElasticApm; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import javax.servlet.Filter; | |
import javax.servlet.FilterChain; | |
import javax.servlet.FilterConfig; | |
import javax.servlet.ServletException; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.ServletResponse; | |
import javax.servlet.http.HttpServletRequest; | |
import java.io.IOException; | |
import java.util.regex.Pattern; | |
/** | |
* Intercepts Wicket AJAX requests to set valid names of Elastic APM transactions. | |
*/ | |
public class AjaxAPMFilter implements Filter { | |
private static final Logger LOG = LoggerFactory.getLogger(AjaxAPMFilter.class); | |
private static final Pattern RESOURCE_ID_PATTERN = Pattern.compile("/\\d+"); | |
private static final char APM_PATH_DELIMITER = ':'; | |
private static final char WICKET_COMPONENT_DELIMITER = '-'; | |
private static final char QUERY_PARAM_DELIMITER = '&'; | |
private static final char WICKET_INTERFACE_PREFIX = 'I'; | |
private static final char TILDE_CHAR = '~'; | |
private static final char UNDERSCORE_CHAR = '_'; | |
@Override | |
public void init(FilterConfig filterConfig) throws ServletException { | |
} | |
@Override | |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | |
throws IOException, ServletException { | |
HttpServletRequest httpRequest = (HttpServletRequest) request; | |
String query = httpRequest.getQueryString(); | |
if ((query != null) && containsWicketListener(query)) { | |
String resourcePath = removeFirstChar(stripResourceIDs(extractResourcePath(httpRequest))); | |
String wicketIdentifier = encodeTildesForKibanaWorkaround(stripParentComponents(extractWicketComponentPath(query))); | |
String transactionName = resourcePath + APM_PATH_DELIMITER + wicketIdentifier; | |
LOG.trace("AJAX transaction name: {}", transactionName); | |
ElasticApm.currentTransaction().setName(transactionName); | |
} | |
chain.doFilter(request, response); | |
} | |
/** | |
* Checks whether the query string mentions one of Wicket AJAX listeners. | |
* | |
* @param queryString a query string | |
* @return true if the string contains an AJAX listener, false otherwise | |
*/ | |
private static boolean containsWicketListener(String queryString) { | |
return queryString.contains("IResourceListener") | |
|| queryString.contains("IBehaviorListener") | |
|| queryString.contains("ILinkListener") | |
|| queryString.contains("IOnChangeListener") | |
|| queryString.contains("IFormSubmitListener"); | |
} | |
/** | |
* Extracts a "Resource" path from an http request. Resource path consists of servletPath and pathInfo, | |
* i.e. omits the contextPath, since that would not provide much relevance. | |
* | |
* @param httpRequest HTTP request to process | |
* @return extracted resource path | |
*/ | |
private static String extractResourcePath(HttpServletRequest httpRequest) { | |
String pathInfo = httpRequest.getPathInfo(); | |
return (pathInfo == null) ? httpRequest.getServletPath() : (httpRequest.getServletPath() + pathInfo); | |
} | |
/** | |
* Removes numeric resource identifiers from a resource path / URL. | |
* | |
* @param resourcePath a resource path to process | |
* @return a resource path without resource identifiers | |
*/ | |
private static String stripResourceIDs(String resourcePath) { | |
return RESOURCE_ID_PATTERN.matcher(resourcePath).replaceAll(""); | |
} | |
/** | |
* Removes a first char from a string. | |
* | |
* @param request string to remove from | |
* @return string without a first char | |
*/ | |
private static String removeFirstChar(String request) { | |
return request.substring(1); | |
} | |
/** | |
* Extracts a wicket component path parameter pointing to target AJAX component from the HTTP query. | |
* | |
* @param query query string to process | |
* @return component path parameter | |
*/ | |
private static String extractWicketComponentPath(String query) { | |
int firstAmpIndex = query.indexOf(QUERY_PARAM_DELIMITER); | |
int firstIIndex = query.indexOf(WICKET_INTERFACE_PREFIX); | |
return (firstAmpIndex > 0) ? query.substring(firstIIndex, firstAmpIndex) : query.substring(firstIIndex); | |
} | |
/** | |
* Removes Wicket parent hierarchy from a component path and leaves only the listener and target component ID. | |
* | |
* @param componentPath Wicket component path to process | |
* @return Wicket AJAX listener identifier without parent hierarchy | |
*/ | |
private static String stripParentComponents(String componentPath) { | |
int firstDashIndex = componentPath.indexOf(WICKET_COMPONENT_DELIMITER); | |
int lastDashIndex = componentPath.lastIndexOf(WICKET_COMPONENT_DELIMITER); | |
return componentPath.substring(0, firstDashIndex) + componentPath.substring(lastDashIndex); | |
} | |
/** | |
* Replace all tildes in wicket component identifier with underscores. This is a workaround for Kibana's special | |
* handling of the tilde character, that gets replaced by the '%' sign in javascript, resulting in an invalid URI. | |
* | |
* @param wicketIdentifier wicket component identifier | |
* @return encoded component identifier | |
*/ | |
private static String encodeTildesForKibanaWorkaround(String wicketIdentifier) { | |
return wicketIdentifier.replace(TILDE_CHAR, UNDERSCORE_CHAR); | |
} | |
@Override | |
public void destroy() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment