/* * Copyright 2011-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.servlet; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nullable; import org.glowroot.agent.plugin.api.ThreadContext; import org.glowroot.agent.plugin.api.ThreadContext.Priority; import org.glowroot.agent.plugin.api.weaving.BindParameter; import org.glowroot.agent.plugin.api.weaving.BindReceiver; import org.glowroot.agent.plugin.api.weaving.OnAfter; import org.glowroot.agent.plugin.api.weaving.Pointcut; import org.glowroot.agent.plugin.servlet.ServletAspect.HttpSession; public class SessionAspect { @Pointcut(className = "javax.servlet.http.HttpSession", methodName = "setAttribute|putValue", methodParameterTypes = {"java.lang.String", "java.lang.Object"}, nestingGroup = "servlet-inner-call") public static class SetAttributeAdvice { @OnAfter public static void onAfter(ThreadContext context, @BindReceiver HttpSession session, @BindParameter @Nullable String name, @BindParameter @Nullable Object value) { if (name == null) { // theoretically possible, so just ignore return; } // name is non-null per HttpSession.setAttribute() javadoc, but value may be null // (which per the javadoc is the same as calling removeAttribute()) ServletMessageSupplier messageSupplier = (ServletMessageSupplier) context.getServletMessageSupplier(); if (messageSupplier != null) { updateUserIfApplicable(context, name, value, session); updateSessionAttributesIfApplicable(messageSupplier, name, value, session); } } private static void updateUserIfApplicable(ThreadContext context, String name, @Nullable Object value, HttpSession session) { if (value == null) { // if user value is set to null, don't clear it return; } String sessionUserAttributePath = ServletPluginProperties.sessionUserAttributePath(); if (!sessionUserAttributePath.isEmpty()) { // capture user now, don't use a lazy supplier if (sessionUserAttributePath.equals(name)) { context.setTransactionUser(value.toString(), Priority.CORE_PLUGIN); } else if (sessionUserAttributePath.startsWith(name + ".")) { String user = HttpSessions.getSessionAttributeTextValue(session, sessionUserAttributePath); if (user != null) { // if user is null, don't clear it by setting Suppliers.ofInstance(null) context.setTransactionUser(user, Priority.CORE_PLUGIN); } } } } private static void updateSessionAttributesIfApplicable( ServletMessageSupplier messageSupplier, String name, @Nullable Object value, HttpSession session) { if (ServletPluginProperties.captureSessionAttributeNames().contains(name) || ServletPluginProperties.captureSessionAttributeNames().contains("*")) { // update all session attributes (possibly nested) at or under the set attribute for (String capturePath : ServletPluginProperties.captureSessionAttributePaths()) { if (capturePath.equals(name) || capturePath.equals("*")) { updateSessionAttribute(messageSupplier, name, value); } else if (capturePath.startsWith(name + ".")) { updateNestedSessionAttributes(messageSupplier, capturePath, value, session); } } } } private static void updateSessionAttribute(ServletMessageSupplier messageSupplier, String name, @Nullable Object value) { if (value == null) { messageSupplier.putSessionAttributeChangedValue(name, null); } else { messageSupplier.putSessionAttributeChangedValue(name, value.toString()); } } private static void updateNestedSessionAttributes(ServletMessageSupplier messageSupplier, String capturePath, @Nullable Object value, HttpSession session) { if (capturePath.endsWith(".*")) { String capturePathBase = capturePath.substring(0, capturePath.length() - 2); Object val = HttpSessions.getSessionAttribute(session, capturePathBase); if (val == null) { messageSupplier.putSessionAttributeChangedValue(capturePathBase, null); } else if (val instanceof Map<?, ?>) { for (Entry<?, ?> entry : ((Map<?, ?>) val).entrySet()) { Object v = entry.getValue(); messageSupplier.putSessionAttributeChangedValue( capturePathBase + "." + entry.getKey(), v == null ? null : v.toString()); } } else { for (Entry<String, String> entry : Beans.propertiesAsText(val).entrySet()) { messageSupplier.putSessionAttributeChangedValue( capturePathBase + "." + entry.getKey(), entry.getValue()); } } } else if (value == null) { // no need to navigate path since it will always be null messageSupplier.putSessionAttributeChangedValue(capturePath, null); } else { String val = HttpSessions.getSessionAttributeTextValue(session, capturePath); messageSupplier.putSessionAttributeChangedValue(capturePath, val); } } } @Pointcut(className = "javax.servlet.http.HttpSession", methodName = "removeAttribute", methodParameterTypes = {"java.lang.String"}, nestingGroup = "servlet-inner-call") public static class RemoveAttributeAdvice { @OnAfter public static void onAfter(ThreadContext context, @BindReceiver HttpSession session, @BindParameter @Nullable String name) { // calling HttpSession.setAttribute() with null value is the same as calling // removeAttribute(), per the setAttribute() javadoc SetAttributeAdvice.onAfter(context, session, name, null); } } }