All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.instrumentation.servlet.boot.ResponseHeaderComponent Maven / Gradle / Ivy

There is a newer version: 0.14.9
Show newest version
/*
 * Copyright 2014-2019 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.instrumentation.servlet.boot;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.glowroot.instrumentation.api.Logger;
import org.glowroot.instrumentation.api.checker.MonotonicNonNull;
import org.glowroot.instrumentation.api.checker.Nullable;

// 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 = Logger.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 responseHeaders;

    synchronized Map getMapOfStrings() {
        if (responseHeaders == null) {
            return Collections.emptyMap();
        }
        // 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 responseHeaderStrings = new HashMap();
        for (ResponseHeader responseHeader : responseHeaders.values()) {
            String name = responseHeader.getName();
            List values = responseHeader.getValues();
            if (values != null) {
                List headerValueStrings = new ArrayList();
                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 = new ConcurrentHashMap();
        }
        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 = new ConcurrentHashMap();
        }
        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 static 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 "";
        }
    }

    // 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 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 getValues() {
            return values;
        }

        private void setValue(Object value) {
            this.value = value;
        }

        private void addValue(Object newValue) {
            if (values == null) {
                values = new CopyOnWriteArrayList();
                values.add(value);
            }
            values.add(newValue);
        }
    }
}