/* * Copyright 2011-2016 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 java.util.concurrent.ConcurrentMap; import javax.annotation.Nullable; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.glowroot.agent.plugin.api.Message; import org.glowroot.agent.plugin.api.MessageSupplier; // this class is thread-safe (unlike other MessageSuppliers) since it gets passed around to // auxiliary thread contexts for handling async servlets class ServletMessageSupplier extends MessageSupplier { private final String requestMethod; private final String requestUri; private final @Nullable String requestQueryString; private volatile @MonotonicNonNull ImmutableMap<String, Object> requestParameters; private final ImmutableMap<String, Object> requestHeaders; private final ResponseHeaderComponent responseHeaderComponent = new ResponseHeaderComponent(); // session attributes may not be thread safe, so they must be converted to Strings // within the request processing thread, which can then be safely read by the trace storage // thread (and live viewing thread also) // the initial value map contains the session attributes as they were present at the beginning // of the request private final ImmutableMap<String, String> sessionAttributeInitialValueMap; // ConcurrentHashMap does not allow null values, so need to use Optional values private volatile @MonotonicNonNull ConcurrentMap<String, Optional<String>> sessionAttributeUpdatedValueMap; ServletMessageSupplier(String requestMethod, String requestUri, @Nullable String requestQueryString, ImmutableMap<String, Object> requestHeaders, ImmutableMap<String, String> sessionAttributeMap) { this.requestMethod = requestMethod; this.requestUri = requestUri; this.requestQueryString = requestQueryString; this.requestHeaders = requestHeaders; this.sessionAttributeInitialValueMap = sessionAttributeMap; } @Override public Message get() { Map<String, Object> detail = Maps.newHashMap(); detail.put("Request http method", requestMethod); if (requestQueryString != null) { // including empty query string since that means request ended with ? detail.put("Request query string", requestQueryString); } if (requestParameters != null && !requestParameters.isEmpty()) { detail.put("Request parameters", requestParameters); } if (!requestHeaders.isEmpty()) { detail.put("Request headers", requestHeaders); } Map<String, Object> responseHeaderStrings = responseHeaderComponent.getMapOfStrings(); if (!responseHeaderStrings.isEmpty()) { detail.put("Response headers", responseHeaderStrings); } addSessionAttributeDetail(detail); return Message.create(requestUri, detail); } boolean isRequestParametersCaptured() { return requestParameters != null; } void setCaptureRequestParameters(ImmutableMap<String, Object> requestParameters) { this.requestParameters = requestParameters; } void setResponseHeader(String name, String value) { responseHeaderComponent.setHeader(name, value); } void setResponseDateHeader(String name, long date) { responseHeaderComponent.setHeader(name, date); } void setResponseIntHeader(String name, int value) { responseHeaderComponent.setHeader(name, value); } void setResponseLongHeader(String name, long value) { responseHeaderComponent.setHeader(name, value); } void addResponseHeader(String name, String value) { responseHeaderComponent.addHeader(name, value); } void addResponseDateHeader(String name, long date) { responseHeaderComponent.addHeader(name, date); } void addResponseIntHeader(String name, int value) { responseHeaderComponent.addHeader(name, value); } void putSessionAttributeChangedValue(String name, @Nullable String value) { if (sessionAttributeUpdatedValueMap == null) { sessionAttributeUpdatedValueMap = Maps.newConcurrentMap(); } sessionAttributeUpdatedValueMap.put(name, Optional.fromNullable(value)); } private void addSessionAttributeDetail(Map<String, Object> detail) { if (!sessionAttributeInitialValueMap.isEmpty()) { if (sessionAttributeUpdatedValueMap == null) { // session attributes were captured at the beginning of the request, and no session // attributes were updated mid-request detail.put("Session attributes", sessionAttributeInitialValueMap); } else { // some session attributes were updated mid-request addMidRequestSessionAttributeDetail(detail); } } else if (sessionAttributeUpdatedValueMap != null) { // no session attributes were available at the beginning of the request, and session // attributes were updated mid-request detail.put("Session attributes (updated during this request)", sessionAttributeUpdatedValueMap); } else { // both initial and updated value maps are null so there is nothing to add to the // detail map } } @RequiresNonNull("sessionAttributeUpdatedValueMap") private void addMidRequestSessionAttributeDetail(Map<String, Object> detail) { Map<String, /*@Nullable*/ Object> sessionAttributeInitialValuePlusMap = Maps.newHashMap(); sessionAttributeInitialValuePlusMap.putAll(sessionAttributeInitialValueMap); // add empty values into initial values for any updated attributes that are not // already present in initial values nested detail map for (Entry<String, Optional<String>> entry : sessionAttributeUpdatedValueMap.entrySet()) { if (!sessionAttributeInitialValueMap.containsKey(entry.getKey())) { sessionAttributeInitialValuePlusMap.put(entry.getKey(), null); } } detail.put("Session attributes (at beginning of this request)", sessionAttributeInitialValuePlusMap); detail.put("Session attributes (updated during this request)", sessionAttributeUpdatedValueMap); } }