001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.impl;
018
019import java.util.concurrent.atomic.AtomicLong;
020import java.util.regex.Matcher;
021import java.util.regex.Pattern;
022
023import org.apache.camel.CamelContext;
024import org.apache.camel.management.JmxSystemPropertyKeys;
025import org.apache.camel.spi.ManagementNameStrategy;
026import org.apache.camel.util.ObjectHelper;
027
028/**
029 * Default implementation of {@link ManagementNameStrategy}
030 * <p/>
031 * This implementation will by default use a name pattern as <tt>#name#</tt> and in case
032 * of a clash, then the pattern will fallback to be using the counter as <tt>#name#-#counter#</tt>.
033 */
034public class DefaultManagementNameStrategy implements ManagementNameStrategy {
035
036    private static final Pattern INVALID_PATTERN = Pattern.compile(".*#\\w+#.*");
037    private static final AtomicLong NAME_COUNTER = new AtomicLong();
038
039    private final CamelContext camelContext;
040    private final String defaultPattern;
041    private final String nextPattern;
042    private String name;
043    private String namePattern;
044
045    public DefaultManagementNameStrategy(CamelContext camelContext) {
046        this(camelContext, System.getProperty(JmxSystemPropertyKeys.MANAGEMENT_NAME_PATTERN, "#name#"), "#name#-#counter#");
047    }
048
049    public DefaultManagementNameStrategy(CamelContext camelContext, String defaultPattern, String nextPattern) {
050        this.camelContext = camelContext;
051        this.defaultPattern = defaultPattern;
052        this.nextPattern = nextPattern;
053    }
054
055    @Override
056    public String getNamePattern() {
057        return namePattern;
058    }
059
060    @Override
061    public void setNamePattern(String namePattern) {
062        this.namePattern = namePattern;
063    }
064
065    @Override
066    public String getName() {
067        if (name == null) {
068            String pattern = getNamePattern();
069            if (pattern == null) {
070                // fallback and use the default pattern which is the same name as the CamelContext has been given
071                pattern = defaultPattern;
072            }
073            name = resolveManagementName(pattern, camelContext.getName(), true);
074        }
075        return name;
076    }
077
078    @Override
079    public String getNextName() {
080        if (isFixedName()) {
081            // use the fixed name
082            return getName();
083        } else {
084            // or resolve a new name
085            String pattern = getNamePattern();
086            if (pattern == null) {
087                // use a pattern that has a counter to ensure unique next name
088                pattern = nextPattern;
089            }
090            return resolveManagementName(pattern, camelContext.getName(), true);
091        }
092    }
093
094    @Override
095    public boolean isFixedName() {
096        // the name will be fixed unless there is a counter token
097        String pattern = getNamePattern();
098        if (pattern == null) {
099            // we are not fixed by default
100            return false;
101        }
102        return !pattern.contains("#counter#");
103    }
104
105    /**
106     * Creates a new management name with the given pattern
107     *
108     * @param pattern the pattern
109     * @param name    the name
110     * @return the management name
111     * @throws IllegalArgumentException if the pattern or name is invalid or empty
112     */
113    public String resolveManagementName(String pattern, String name, boolean invalidCheck) {
114        ObjectHelper.notEmpty(pattern, "pattern");
115        ObjectHelper.notEmpty(name, "name");
116
117        // must quote the names to have it work as literal replacement
118        name = Matcher.quoteReplacement(name);
119
120        // replace tokens
121        String answer = pattern;
122        if (pattern.contains("#counter#")) {
123            // only increment the counter on-demand
124            answer = pattern.replaceFirst("#counter#", "" + nextNameCounter());
125        }
126        // camelId and name is the same tokens
127        answer = answer.replaceFirst("#camelId#", name);
128        answer = answer.replaceFirst("#name#", name);
129
130        // allow custom name resolution as well. For example with camel-core-osgi we have a custom
131        // name strategy that supports OSGI specific tokens such as #bundleId# etc.
132        answer = customResolveManagementName(pattern, answer);
133
134        // are there any #word# combos left, if so they should be considered invalid tokens
135        if (invalidCheck && INVALID_PATTERN.matcher(answer).matches()) {
136            throw new IllegalArgumentException("Pattern is invalid: " + pattern);
137        }
138
139        return answer;
140    }
141
142    /**
143     * Strategy to do any custom resolution of the name
144     *
145     * @param pattern  the pattern
146     * @param answer   the current answer, which may have custom patterns still to be resolved
147     * @return the resolved name
148     */
149    protected String customResolveManagementName(String pattern, String answer) {
150        return answer;
151    }
152
153    private static long nextNameCounter() {
154        // we want to be 1-based, so increment first
155        return NAME_COUNTER.incrementAndGet();
156    }
157
158    /**
159     * To reset the counter, should only be used for testing purposes.
160     *
161     * @param value the counter value
162     */
163    public static void setCounter(int value) {
164        NAME_COUNTER.set(value);
165    }
166
167}