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.spi.ManagementNameStrategy;
025import org.apache.camel.util.ObjectHelper;
026
027/**
028 * Default implementation of {@link ManagementNameStrategy}
029 * <p/>
030 * This implementation will by default use a name pattern as <tt>#name#</tt> and in case
031 * of a clash, then the pattern will fallback to be using the counter as <tt>#name#-#counter#</tt>.
032 */
033public class DefaultManagementNameStrategy implements ManagementNameStrategy {
034
035    private static final Pattern INVALID_PATTERN = Pattern.compile(".*#\\w+#.*");
036    private static final AtomicLong NAME_COUNTER = new AtomicLong();
037
038    private final CamelContext camelContext;
039    private final String defaultPattern;
040    private final String nextPattern;
041    private String name;
042    private String namePattern;
043
044    public DefaultManagementNameStrategy(CamelContext camelContext) {
045        this(camelContext, null, "#name#-#counter#");
046    }
047
048    public DefaultManagementNameStrategy(CamelContext camelContext, String defaultPattern, String nextPattern) {
049        this.camelContext = camelContext;
050        this.defaultPattern = defaultPattern;
051        this.nextPattern = nextPattern;
052    }
053
054    @Override
055    public String getNamePattern() {
056        return namePattern;
057    }
058
059    @Override
060    public void setNamePattern(String namePattern) {
061        this.namePattern = namePattern;
062    }
063
064    @Override
065    public String getName() {
066        if (name == null) {
067            String pattern = getNamePattern();
068            if (pattern == null) {
069                // fallback and use the default pattern which is the same name as the CamelContext has been given
070                pattern = defaultPattern != null ? defaultPattern : camelContext.getManagementStrategy().getManagementAgent().getManagementNamePattern();
071            }
072            name = resolveManagementName(pattern, camelContext.getName(), true);
073        }
074        return name;
075    }
076
077    @Override
078    public String getNextName() {
079        if (isFixedName()) {
080            // use the fixed name
081            return getName();
082        } else {
083            // or resolve a new name
084            String pattern = getNamePattern();
085            if (pattern == null) {
086                // use a pattern that has a counter to ensure unique next name
087                pattern = nextPattern;
088            }
089            return resolveManagementName(pattern, camelContext.getName(), true);
090        }
091    }
092
093    @Override
094    public boolean isFixedName() {
095        // the name will be fixed unless there is a counter token
096        String pattern = getNamePattern();
097        if (pattern == null) {
098            // we are not fixed by default
099            return false;
100        }
101        return !pattern.contains("#counter#");
102    }
103
104    /**
105     * Creates a new management name with the given pattern
106     *
107     * @param pattern the pattern
108     * @param name    the name
109     * @return the management name
110     * @throws IllegalArgumentException if the pattern or name is invalid or empty
111     */
112    public String resolveManagementName(String pattern, String name, boolean invalidCheck) {
113        ObjectHelper.notEmpty(pattern, "pattern");
114        ObjectHelper.notEmpty(name, "name");
115
116        // must quote the names to have it work as literal replacement
117        name = Matcher.quoteReplacement(name);
118
119        // replace tokens
120        String answer = pattern;
121        if (pattern.contains("#counter#")) {
122            // only increment the counter on-demand
123            answer = pattern.replaceFirst("#counter#", "" + nextNameCounter());
124        }
125        // camelId and name is the same tokens
126        answer = answer.replaceFirst("#camelId#", name);
127        answer = answer.replaceFirst("#name#", name);
128
129        // allow custom name resolution as well. For example with camel-core-osgi we have a custom
130        // name strategy that supports OSGI specific tokens such as #bundleId# etc.
131        answer = customResolveManagementName(pattern, answer);
132
133        // are there any #word# combos left, if so they should be considered invalid tokens
134        if (invalidCheck && INVALID_PATTERN.matcher(answer).matches()) {
135            throw new IllegalArgumentException("Pattern is invalid: " + pattern);
136        }
137
138        return answer;
139    }
140
141    /**
142     * Strategy to do any custom resolution of the name
143     *
144     * @param pattern  the pattern
145     * @param answer   the current answer, which may have custom patterns still to be resolved
146     * @return the resolved name
147     */
148    protected String customResolveManagementName(String pattern, String answer) {
149        return answer;
150    }
151
152    private static long nextNameCounter() {
153        // we want to be 1-based, so increment first
154        return NAME_COUNTER.incrementAndGet();
155    }
156
157    /**
158     * To reset the counter, should only be used for testing purposes.
159     *
160     * @param value the counter value
161     */
162    public static void setCounter(int value) {
163        NAME_COUNTER.set(value);
164    }
165
166}