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;
020
021import org.apache.camel.spi.UuidGenerator;
022import org.apache.camel.util.InetAddressUtil;
023import org.apache.camel.util.ObjectHelper;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027/**
028 * Default {@link UuidGenerator} that is based on the {@link ActiveMQUuidGenerator} but
029 * is optimized for Camel usage to startup faster and avoid use local network binding to obtain a random number.
030 */
031public class DefaultUuidGenerator implements UuidGenerator {
032
033    private static final Logger LOG = LoggerFactory.getLogger(DefaultUuidGenerator.class);
034    private static final String UNIQUE_STUB;
035    private static int instanceCount;
036    private static String hostName;
037    private String seed;
038    // must use AtomicLong to ensure atomic get and update operation that is thread-safe
039    private final AtomicLong sequence = new AtomicLong(1);
040    private final int length;
041
042    static {
043        String stub = "";
044        boolean canAccessSystemProps = true;
045        try {
046            SecurityManager sm = System.getSecurityManager();
047            if (sm != null) {
048                sm.checkPropertiesAccess();
049            }
050        } catch (SecurityException se) {
051            canAccessSystemProps = false;
052        }
053
054        if (canAccessSystemProps) {
055            try {
056                if (hostName == null) {
057                    hostName = InetAddressUtil.getLocalHostName();
058                }
059                stub = "-" + System.currentTimeMillis() + "-";
060            } catch (Exception e) {
061                if (LOG.isTraceEnabled()) {
062                    LOG.trace("Cannot generate unique stub by using DNS", e);
063                } else {
064                    LOG.warn("Cannot generate unique stub by using DNS due {}. This exception is ignored.", e.getMessage());
065                }
066            }
067        }
068
069        // fallback to use localhost
070        if (hostName == null) {
071            hostName = "localhost";
072        }
073        hostName = sanitizeHostName(hostName);
074
075        if (ObjectHelper.isEmpty(stub)) {
076            stub = "-1-" + System.currentTimeMillis() + "-";
077        }
078        UNIQUE_STUB = stub;
079    }
080
081    public DefaultUuidGenerator(String prefix) {
082        synchronized (UNIQUE_STUB) {
083            this.seed = prefix + UNIQUE_STUB + (instanceCount++) + "-";
084            // let the ID be friendly for URL and file systems
085            this.seed = generateSanitizedId(this.seed);
086            this.length = seed.length() + ("" + Long.MAX_VALUE).length();
087        }
088    }
089
090    public DefaultUuidGenerator() {
091        this("ID-" + hostName);
092    }
093
094    /**
095     * As we have to find the hostname as a side-affect of generating a unique
096     * stub, we allow it's easy retrieval here
097     * 
098     * @return the local host name
099     */
100    public static String getHostName() {
101        return hostName;
102    }
103
104    public static String sanitizeHostName(String hostName) {
105        boolean changed = false;
106
107        StringBuilder sb = new StringBuilder();
108        for (char ch : hostName.toCharArray()) {
109            // only include ASCII chars
110            if (ch < 127) {
111                sb.append(ch);
112            } else {
113                changed = true;
114            }
115        }
116
117        if (changed) {
118            String newHost = sb.toString();
119            LOG.info("Sanitized hostname from: {} to: {}", hostName, newHost);
120            return newHost;
121        } else {
122            return hostName;
123        }
124    }
125
126    public String generateUuid() {
127        StringBuilder sb = new StringBuilder(length);
128        sb.append(seed);
129        sb.append(sequence.getAndIncrement());
130        return sb.toString();
131    }
132
133    /**
134     * Generate a unique ID - that is friendly for a URL or file system
135     * 
136     * @return a unique id
137     */
138    public String generateSanitizedId() {
139        return generateSanitizedId(generateUuid());
140    }
141
142    /**
143     * Ensures that the id is friendly for a URL or file system
144     *
145     * @param id the unique id
146     * @return the id as file friendly id
147     */
148    public static String generateSanitizedId(String id) {
149        id = id.replace(':', '-');
150        id = id.replace('_', '-');
151        id = id.replace('.', '-');
152        id = id.replace('/', '-');
153        return id;
154    }
155}