/** * Copyright 2014 Netflix, Inc. * <p/> * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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 com.netflix.servo.jmx; import com.netflix.servo.monitor.Monitor; import javax.management.ObjectName; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * An {@link ObjectNameMapper} that allows the order of * tags to be specified when constructing the {@link ObjectName}. * The mapper will map the known ordered tag keys and then optionally * append the remaining tags. While an {@link ObjectName}'s properties * are meant to be unordered some visual tools such as VisualVM use the * given order to build a hierarchy. This ordering allows that hierarchy * to be manipulated. * <p/> * It is recommended to always append the remaining tags to avoid collisions * in the generated {@link ObjectName}. The mapper remaps any characters that * are not alphanumeric, a period, or hypen to an underscore. */ public final class OrderedObjectNameMapper implements ObjectNameMapper { private final List<String> keyOrder; private final boolean appendRemaining; private final boolean orderIncludesName; /** * Creates the mapper specifying the order of keys to use and whether * non-explicitly mentioned tag keys should then be appended or not to * the resulting {@link ObjectName}. * * @param appendRemaining whether to append the remaining tags * @param orderedKeys the keys in order that should be used */ public OrderedObjectNameMapper(boolean appendRemaining, String... orderedKeys) { this(appendRemaining, Arrays.asList(orderedKeys)); } /** * Creates the mapper specifying the order of keys to use and whether * non-explicitly mentioned tag keys should then be appended or not to * the resulting {@link ObjectName}. * * @param appendRemaining whether to append the remaining tags * @param orderedKeys the list of keys in the order that should be used */ public OrderedObjectNameMapper(boolean appendRemaining, List<String> orderedKeys) { this.keyOrder = new ArrayList<>(orderedKeys); this.appendRemaining = appendRemaining; this.orderIncludesName = keyOrder.contains("name"); } @Override public ObjectName createObjectName(String domain, Monitor<?> monitor) { ObjectNameBuilder objBuilder = ObjectNameBuilder.forDomain(domain); Map<String, String> tags = new TreeMap<>( monitor.getConfig().getTags().asMap()); // For the known ordered keys, try to add them if they're present in the monitor's tags for (String knownKey : keyOrder) { // Special case for name as it isn't a tag if ("name".equals(knownKey)) { addName(objBuilder, monitor); } else { String value = tags.remove(knownKey); if (value != null) { objBuilder.addProperty(knownKey, value); } } } // If appending, then add the name (if not already added) and remaining tags if (appendRemaining) { if (!orderIncludesName) { addName(objBuilder, monitor); } for (Map.Entry<String, String> additionalTag : tags.entrySet()) { objBuilder.addProperty(additionalTag.getKey(), additionalTag.getValue()); } } return objBuilder.build(); } private void addName(ObjectNameBuilder builder, Monitor<?> monitor) { builder.addProperty("name", monitor.getConfig().getName()); } }