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.io.IOException;
020import java.net.ServerSocket;
021import java.util.concurrent.atomic.AtomicLong;
022
023import org.apache.camel.spi.UuidGenerator;
024import org.apache.camel.util.InetAddressUtil;
025import org.apache.camel.util.ObjectHelper;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * {@link org.apache.camel.spi.UuidGenerator} which is a fast implementation based on
031 * how <a href="http://activemq.apache.org/">Apache ActiveMQ</a> generates its UUID.
032 * <p/>
033 * This implementation is not synchronized but it leverages API which may not be accessible
034 * in the cloud (such as Google App Engine).
035 * <p/>
036 * The JVM system property {@link #PROPERTY_IDGENERATOR_PORT} can be used to set a specific port
037 * number to be used as part of the initialization process to generate unique UUID.
038 */
039public class ActiveMQUuidGenerator implements UuidGenerator {
040
041    // use same JVM property name as ActiveMQ
042    public static final String PROPERTY_IDGENERATOR_PORT = "activemq.idgenerator.port";
043    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQUuidGenerator.class);
044    private static final String UNIQUE_STUB;
045    private static int instanceCount;
046    private static String hostName;
047    private String seed;
048    private final AtomicLong sequence = new AtomicLong(1);
049    private final int length;
050
051    static {
052        String stub = "";
053        boolean canAccessSystemProps = true;
054        try {
055            SecurityManager sm = System.getSecurityManager();
056            if (sm != null) {
057                sm.checkPropertiesAccess();
058            }
059        } catch (SecurityException se) {
060            canAccessSystemProps = false;
061        }
062
063        if (canAccessSystemProps) {
064            int idGeneratorPort = 0;
065            ServerSocket ss = null;
066            try {
067                idGeneratorPort = Integer.parseInt(System.getProperty(PROPERTY_IDGENERATOR_PORT, "0"));
068                LOG.trace("Using port {}", idGeneratorPort);
069                hostName = InetAddressUtil.getLocalHostName();
070                ss = new ServerSocket(idGeneratorPort);
071                stub = "-" + ss.getLocalPort() + "-" + System.currentTimeMillis() + "-";
072                Thread.sleep(100);
073            } catch (Exception e) {
074                if (LOG.isTraceEnabled()) {
075                    LOG.trace("Cannot generate unique stub by using DNS and binding to local port: " + idGeneratorPort, e);
076                } else {
077                    LOG.warn("Cannot generate unique stub by using DNS and binding to local port: " + idGeneratorPort + " due " + e.getMessage());
078                }
079                // Restore interrupted state so higher level code can deal with it.
080                if (e instanceof InterruptedException) {
081                    Thread.currentThread().interrupt();
082                }
083            } finally {
084                // some environments, such as a PaaS may not allow us to create the ServerSocket
085                if (ss != null) {
086                    try {
087                        // TODO: replace the following line with IOHelper.close(ss) when Java 6 support is dropped
088                        ss.close();
089                    } catch (IOException ioe) {
090                        if (LOG.isTraceEnabled()) {
091                            LOG.trace("Closing the server socket failed", ioe);
092                        } else {
093                            LOG.warn("Closing the server socket failed due " + ioe.getMessage() + ". This exception is ignored.");
094                        }
095                    }
096                }
097            }
098        }
099
100        // fallback to use localhost
101        if (hostName == null) {
102            hostName = "localhost";
103        }
104        hostName = sanitizeHostName(hostName);
105
106        if (ObjectHelper.isEmpty(stub)) {
107            stub = "-1-" + System.currentTimeMillis() + "-";
108        }
109        UNIQUE_STUB = stub;
110    }
111
112    public ActiveMQUuidGenerator(String prefix) {
113        synchronized (UNIQUE_STUB) {
114            this.seed = prefix + UNIQUE_STUB + (instanceCount++) + "-";
115            // let the ID be friendly for URL and file systems
116            this.seed = generateSanitizedId(this.seed);
117            this.length = seed.length() + ("" + Long.MAX_VALUE).length();
118        }
119    }
120
121    public ActiveMQUuidGenerator() {
122        this("ID-" + hostName);
123    }
124
125    /**
126     * As we have to find the hostname as a side-affect of generating a unique
127     * stub, we allow it's easy retrieval here
128     * 
129     * @return the local host name
130     */
131    public static String getHostName() {
132        return hostName;
133    }
134
135    public static String sanitizeHostName(String hostName) {
136        boolean changed = false;
137
138        StringBuilder sb = new StringBuilder();
139        for (char ch : hostName.toCharArray()) {
140            // only include ASCII chars
141            if (ch < 127) {
142                sb.append(ch);
143            } else {
144                changed = true;
145            }
146        }
147
148        if (changed) {
149            String newHost = sb.toString();
150            LOG.info("Sanitized hostname from: {} to: {}", hostName, newHost);
151            return newHost;
152        } else {
153            return hostName;
154        }
155    }
156
157    public String generateUuid() {
158        StringBuilder sb = new StringBuilder(length);
159        sb.append(seed);
160        sb.append(sequence.getAndIncrement());
161        return sb.toString();
162    }
163
164    /**
165     * Generate a unique ID - that is friendly for a URL or file system
166     * 
167     * @return a unique id
168     */
169    public String generateSanitizedId() {
170        return generateSanitizedId(generateUuid());
171    }
172
173    /**
174     * Ensures that the id is friendly for a URL or file system
175     *
176     * @param id the unique id
177     * @return the id as file friendly id
178     */
179    public static String generateSanitizedId(String id) {
180        id = id.replace(':', '-');
181        id = id.replace('_', '-');
182        id = id.replace('.', '-');
183        id = id.replace('/', '-');
184        return id;
185    }
186}