001/*
002 * Copyright (C) 2015-2023 The Prometheus jmx_exporter Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package io.prometheus.jmx;
018
019import io.prometheus.jmx.logger.Logger;
020import io.prometheus.jmx.logger.LoggerFactory;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.logging.Level;
028import javax.management.MalformedObjectNameException;
029import javax.management.ObjectName;
030
031/** Class to implement filtering of an MBean's attributes based on the attribute's name */
032@SuppressWarnings("unchecked")
033public class ObjectNameAttributeFilter {
034
035    private static final Logger LOGGER = LoggerFactory.getLogger(ObjectNameAttributeFilter.class);
036
037    /** Configuration constant to define a mapping of ObjectNames to attribute names */
038    public static final String EXCLUDE_OBJECT_NAME_ATTRIBUTES = "excludeObjectNameAttributes";
039
040    /** Configuration constant to enable auto ObjectName attributes filtering */
041    public static final String AUTO_EXCLUDE_OBJECT_NAME_ATTRIBUTES =
042            "autoExcludeObjectNameAttributes";
043
044    private final Map<ObjectName, Set<String>> excludeObjectNameAttributesMap;
045
046    private boolean autoExcludeObjectNameAttributes;
047
048    /** Constructor */
049    private ObjectNameAttributeFilter() {
050        excludeObjectNameAttributesMap = new ConcurrentHashMap<>();
051    }
052
053    /**
054     * Method to initialize the ObjectNameAttributeFilter
055     *
056     * @param yamlConfig yamlConfig
057     * @return an ObjectNameAttributeFilter
058     * @throws MalformedObjectNameException MalformedObjectNameException
059     */
060    private ObjectNameAttributeFilter initialize(Map<String, Object> yamlConfig)
061            throws MalformedObjectNameException {
062        if (yamlConfig.containsKey(EXCLUDE_OBJECT_NAME_ATTRIBUTES)) {
063            Map<Object, Object> objectNameAttributeMap =
064                    (Map<Object, Object>) yamlConfig.get(EXCLUDE_OBJECT_NAME_ATTRIBUTES);
065
066            for (Map.Entry<Object, Object> entry : objectNameAttributeMap.entrySet()) {
067                ObjectName objectName = new ObjectName((String) entry.getKey());
068
069                List<String> attributeNames = (List<String>) entry.getValue();
070
071                Set<String> attributeNameSet =
072                        excludeObjectNameAttributesMap.computeIfAbsent(
073                                objectName, o -> Collections.synchronizedSet(new HashSet<>()));
074
075                attributeNameSet.addAll(attributeNames);
076                for (String attribueName : attributeNames) {
077                    attributeNameSet.add(attribueName);
078                }
079
080                excludeObjectNameAttributesMap.put(objectName, attributeNameSet);
081            }
082        }
083
084        if (yamlConfig.containsKey(AUTO_EXCLUDE_OBJECT_NAME_ATTRIBUTES)) {
085            autoExcludeObjectNameAttributes =
086                    (Boolean) yamlConfig.get(AUTO_EXCLUDE_OBJECT_NAME_ATTRIBUTES);
087        } else {
088            autoExcludeObjectNameAttributes = true;
089        }
090
091        LOGGER.log(Level.FINE, "dynamicExclusion [%b]", autoExcludeObjectNameAttributes);
092
093        return this;
094    }
095
096    /**
097     * Method to add an attribute name to the filter if dynamic exclusion is enabled
098     *
099     * @param objectName the ObjectName
100     * @param attributeName the attribute name
101     */
102    public void add(ObjectName objectName, String attributeName) {
103        if (autoExcludeObjectNameAttributes) {
104            Set<String> attribteNameSet =
105                    excludeObjectNameAttributesMap.computeIfAbsent(
106                            objectName, o -> Collections.synchronizedSet(new HashSet<>()));
107
108            LOGGER.log(
109                    Level.FINE,
110                    "auto adding exclusion of object name [%s] attribute name [%s]",
111                    objectName.getCanonicalName(),
112                    attributeName);
113
114            attribteNameSet.add(attributeName);
115        }
116    }
117
118    /**
119     * Method to check if an attribute should be excluded
120     *
121     * @param objectName the ObjectName
122     * @param attributeName the attribute name
123     * @return true if it should be excluded, false otherwise
124     */
125    public boolean exclude(ObjectName objectName, String attributeName) {
126        boolean result = false;
127
128        if (excludeObjectNameAttributesMap.size() > 0) {
129            Set<String> attributeNameSet = excludeObjectNameAttributesMap.get(objectName);
130            if (attributeNameSet != null) {
131                result = attributeNameSet.contains(attributeName);
132            }
133        }
134
135        return result;
136    }
137
138    /**
139     * Method to create an ObjectNameAttributeFilter
140     *
141     * @param yamlConfig yamlConfig
142     * @return an ObjectNameAttributeFilter
143     */
144    public static ObjectNameAttributeFilter create(Map<String, Object> yamlConfig) {
145        try {
146            return new ObjectNameAttributeFilter().initialize(yamlConfig);
147        } catch (MalformedObjectNameException e) {
148            throw new RuntimeException(
149                    "Invalid configuration format for excludeObjectNameAttributes", e);
150        }
151    }
152}