/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed 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.glowroot.agent.plugin.jaxrs;
import javax.annotation.Nullable;
import org.glowroot.agent.plugin.api.Agent;
import org.glowroot.agent.plugin.api.MessageSupplier;
import org.glowroot.agent.plugin.api.ThreadContext;
import org.glowroot.agent.plugin.api.ThreadContext.Priority;
import org.glowroot.agent.plugin.api.TimerName;
import org.glowroot.agent.plugin.api.TraceEntry;
import org.glowroot.agent.plugin.api.config.BooleanProperty;
import org.glowroot.agent.plugin.api.util.FastThreadLocal;
import org.glowroot.agent.plugin.api.weaving.BindMethodMeta;
import org.glowroot.agent.plugin.api.weaving.BindParameter;
import org.glowroot.agent.plugin.api.weaving.BindThrowable;
import org.glowroot.agent.plugin.api.weaving.BindTraveler;
import org.glowroot.agent.plugin.api.weaving.OnAfter;
import org.glowroot.agent.plugin.api.weaving.OnBefore;
import org.glowroot.agent.plugin.api.weaving.OnReturn;
import org.glowroot.agent.plugin.api.weaving.OnThrow;
import org.glowroot.agent.plugin.api.weaving.Pointcut;
import org.glowroot.agent.plugin.api.weaving.Shim;
// TODO optimize away servletPath thread local, e.g. store servlet path in thread context via
// servlet plugin and retrieve here
public class ResourceAspect {
@SuppressWarnings("nullness:type.argument.type.incompatible")
private static final FastThreadLocal<RequestInfo> requestInfoHolder =
new FastThreadLocal<RequestInfo>() {
@Override
protected RequestInfo initialValue() {
return new RequestInfo();
}
};
private static final BooleanProperty useAltTransactionNaming =
Agent.getConfigService("jaxrs").getBooleanProperty("useAltTransactionNaming");
@Shim("javax.servlet.http.HttpServletRequest")
public interface HttpServletRequest {
@Nullable
String getMethod();
@Nullable
String getContextPath();
@Nullable
String getServletPath();
@Nullable
String getPathInfo();
}
@Pointcut(className = "javax.servlet.Servlet",
subTypeRestriction = "org.glassfish.jersey.servlet.ServletContainer",
methodName = "service", methodParameterTypes = {"javax.servlet.ServletRequest",
"javax.servlet.ServletResponse"})
public static class CaptureServletPathAdvice {
@OnBefore
public static @Nullable RequestInfo onBefore(@BindParameter @Nullable Object req) {
if (req == null || !(req instanceof HttpServletRequest)) {
return null;
}
HttpServletRequest request = (HttpServletRequest) req;
String contextPath = request.getContextPath();
if (contextPath == null) {
contextPath = "";
}
String pathInfo = request.getPathInfo();
String servletPath;
if (pathInfo == null) {
// pathInfo is null when the dispatcher servlet is mapped to "/" (not "/*") and
// therefore it is replacing the default servlet and getServletPath() returns the
// full path
servletPath = contextPath;
} else {
servletPath = contextPath + request.getServletPath();
}
RequestInfo requestInfo = requestInfoHolder.get();
requestInfo.method = request.getMethod();
requestInfo.servletPath = servletPath;
return requestInfo;
}
@OnAfter
public static void onAfter(
@BindTraveler @Nullable RequestInfo requestInfo) {
if (requestInfo != null) {
requestInfo.method = null;
requestInfo.servletPath = null;
}
}
}
@Pointcut(classAnnotation = "javax.ws.rs.Path",
methodAnnotation = "javax.ws.rs.Path|javax.ws.rs.DELETE|javax.ws.rs.GET"
+ "|javax.ws.rs.HEAD|javax.ws.rs.OPTIONS|javax.ws.rs.POST|javax.ws.rs.PUT",
methodParameterTypes = {".."}, timerName = "jaxrs resource")
public static class ResourceAdvice {
private static final TimerName timerName = Agent.getTimerName(ResourceAdvice.class);
@OnBefore
public static TraceEntry onBefore(ThreadContext context,
@BindMethodMeta ResourceMethodMeta resourceMethodMeta) {
if (useAltTransactionNaming.value()) {
context.setTransactionName(resourceMethodMeta.getAltTransactionName(),
Priority.CORE_PLUGIN);
} else {
RequestInfo requestInfo = requestInfoHolder.get();
String transactionName = getTransactionName(requestInfo.method,
requestInfo.servletPath, resourceMethodMeta.getPath());
context.setTransactionName(transactionName, Priority.CORE_PLUGIN);
}
return context.startTraceEntry(MessageSupplier.create("jaxrs resource: {}.{}()",
resourceMethodMeta.getResourceClassName(), resourceMethodMeta.getMethodName()),
timerName);
}
@OnReturn
public static void onReturn(@BindTraveler TraceEntry traceEntry) {
traceEntry.end();
}
@OnThrow
public static void onThrow(@BindThrowable Throwable t,
@BindTraveler TraceEntry traceEntry) {
traceEntry.endWithError(t);
}
private static String getTransactionName(@Nullable String method,
@Nullable String servletPath, String resourcePath) {
if (method != null) {
if (servletPath == null || servletPath.isEmpty()) {
return method + " " + resourcePath;
} else {
return method + " " + servletPath + resourcePath;
}
} else {
if (servletPath == null || servletPath.isEmpty()) {
return resourcePath;
} else {
return servletPath + resourcePath;
}
}
}
}
private static class RequestInfo {
private @Nullable String method;
private @Nullable String servletPath;
}
}