/* * Copyright 2014-2015 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.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nullable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.glowroot.agent.plugin.api.Agent; import org.glowroot.agent.plugin.api.Logger; // this class is thread safe since it is part of the thread safe ServletMessageSupplier (see comment // in ServletMessageSupplier for details why) class ResponseHeaderComponent { private static final Logger logger = Agent.getLogger(ServletMessageSupplier.class); // HTTP response header date format (RFC 1123) // also see org.apache.tomcat.util.http.FastHttpDateFormat private static final SimpleDateFormat responseHeaderDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); static { // all HTTP dates are on GMT responseHeaderDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } // map key is uppercase for case-insensitivity private @MonotonicNonNull ConcurrentMap<String, ResponseHeader> responseHeaders; synchronized Map<String, Object> getMapOfStrings() { if (responseHeaders == null) { return ImmutableMap.of(); } // lazy format int and date headers as strings since this method is only called if // it is really needed (for storage or display of active trace) Map<String, Object> responseHeaderStrings = Maps.newHashMap(); for (ResponseHeader responseHeader : responseHeaders.values()) { String name = responseHeader.getName(); List<Object> values = responseHeader.getValues(); if (values != null) { List<String> headerValueStrings = Lists.newArrayList(); for (Object v : values) { headerValueStrings.add(headerValueString(v)); } responseHeaderStrings.put(name, headerValueStrings); } else { Object value = responseHeader.getValue(); responseHeaderStrings.put(name, headerValueString(value)); } } return responseHeaderStrings; } synchronized void setHeader(String name, Object value) { if (responseHeaders == null) { responseHeaders = Maps.newConcurrentMap(); } String nameUpper = name.toUpperCase(Locale.ENGLISH); ResponseHeader responseHeader = responseHeaders.get(nameUpper); if (responseHeader == null) { responseHeaders.put(nameUpper, new ResponseHeader(name, value)); } else { responseHeader.setValue(value); } } synchronized void addHeader(String name, Object value) { if (responseHeaders == null) { responseHeaders = Maps.newConcurrentMap(); } String nameUpper = name.toUpperCase(Locale.ENGLISH); ResponseHeader responseHeader = responseHeaders.get(nameUpper); if (responseHeader == null) { responseHeaders.put(nameUpper, new ResponseHeader(name, value)); } else { responseHeader.addValue(value); } } private String headerValueString(Object value) { if (value instanceof String) { return (String) value; } else if (value instanceof Integer) { return Integer.toString((Integer) value); } else if (value instanceof Long) { // this is only called on traces that need to be stored (or an active trace being viewed // inflight in the UI), so date format overhead seems acceptable synchronized (responseHeaderDateFormat) { return responseHeaderDateFormat.format(new Date((Long) value)); } } else { logger.warn("unexpected value type found: {}", value); return "<unexpected value type: " + value.getClass() + ">"; } } // this class is thread safe since it is part of the thread safe ServletMessageSupplier (see // comment in ServletMessageSupplier for details why) private static class ResponseHeader { private final String name; private volatile Object value; private volatile @MonotonicNonNull CopyOnWriteArrayList<Object> values; private ResponseHeader(String name, Object value) { this.name = name; this.value = value; } private String getName() { return name; } private Object getValue() { return value; } private @Nullable List<Object> getValues() { return values; } private void setValue(Object value) { this.value = value; } private void addValue(Object newValue) { if (values == null) { values = Lists.newCopyOnWriteArrayList(); values.add(value); } values.add(newValue); } } }