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.activemq.util;
018
019import java.io.IOException;
020import java.net.ServerSocket;
021import java.util.concurrent.atomic.AtomicLong;
022
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * Generator for Globally unique Strings.
028 */
029public class IdGenerator {
030
031    private static final Logger LOG = LoggerFactory.getLogger(IdGenerator.class);
032    private static final String UNIQUE_STUB;
033    private static int instanceCount;
034    private static String hostName;
035    private String seed;
036    private final AtomicLong sequence = new AtomicLong(1);
037    private int length;
038    public static final String PROPERTY_IDGENERATOR_PORT ="activemq.idgenerator.port";
039
040    static {
041        String stub = "";
042        boolean canAccessSystemProps = true;
043        try {
044            SecurityManager sm = System.getSecurityManager();
045            if (sm != null) {
046                sm.checkPropertiesAccess();
047            }
048        } catch (SecurityException se) {
049            canAccessSystemProps = false;
050        }
051
052        if (canAccessSystemProps) {
053            int idGeneratorPort = 0;
054            ServerSocket ss = null;
055            try {
056                idGeneratorPort = Integer.parseInt(System.getProperty(PROPERTY_IDGENERATOR_PORT, "0"));
057                LOG.trace("Using port {}", idGeneratorPort);
058                hostName = InetAddressUtil.getLocalHostName();
059                ss = new ServerSocket(idGeneratorPort);
060                stub = "-" + ss.getLocalPort() + "-" + System.currentTimeMillis() + "-";
061                Thread.sleep(100);
062            } catch (Exception e) {
063                if (LOG.isTraceEnabled()) {
064                    LOG.trace("could not generate unique stub by using DNS and binding to local port", e);
065                } else {
066                    LOG.warn("could not generate unique stub by using DNS and binding to local port: {} {}", e.getClass().getCanonicalName(), e.getMessage());
067                }
068
069                // Restore interrupted state so higher level code can deal with it.
070                if (e instanceof InterruptedException) {
071                    Thread.currentThread().interrupt();
072                }
073            } finally {
074                if (ss != null) {
075                    try {
076                        // TODO: replace the following line with IOHelper.close(ss) when Java 6 support is dropped
077                        ss.close();
078                    } catch (IOException ioe) {
079                        if (LOG.isTraceEnabled()) {
080                            LOG.trace("Closing the server socket failed", ioe);
081                        } else {
082                            LOG.warn("Closing the server socket failed" + " due " + ioe.getMessage());
083                        }
084                    }
085                }
086            }
087        }
088        // fallback
089        if (hostName == null) {
090            hostName = "localhost";
091        }
092        hostName = sanitizeHostName(hostName);
093
094        if (stub.length() == 0) {
095            stub = "-1-" + System.currentTimeMillis() + "-";
096        }
097        UNIQUE_STUB = stub;
098    }
099
100    /**
101     * Construct an IdGenerator
102     */
103    public IdGenerator(String prefix) {
104        synchronized (UNIQUE_STUB) {
105            this.seed = prefix + UNIQUE_STUB + (instanceCount++) + ":";
106            this.length = this.seed.length() + ("" + Long.MAX_VALUE).length();
107        }
108    }
109
110    public IdGenerator() {
111        this("ID:" + hostName);
112    }
113
114    /**
115     * As we have to find the hostname as a side-affect of generating a unique
116     * stub, we allow it's easy retrieval here
117     *
118     * @return the local host name
119     */
120    public static String getHostName() {
121        return hostName;
122    }
123
124    /**
125     * Generate a unique id
126     *
127     * @return a unique id
128     */
129    public synchronized String generateId() {
130        StringBuilder sb = new StringBuilder(length);
131        sb.append(seed);
132        sb.append(sequence.getAndIncrement());
133        return sb.toString();
134    }
135
136    public static String sanitizeHostName(String hostName) {
137        boolean changed = false;
138
139        StringBuilder sb = new StringBuilder();
140        for (char ch : hostName.toCharArray()) {
141            // only include ASCII chars
142            if (ch < 127) {
143                sb.append(ch);
144            } else {
145                changed = true;
146            }
147        }
148
149        if (changed) {
150            String newHost = sb.toString();
151            LOG.info("Sanitized hostname from: {} to: {}", hostName, newHost);
152            return newHost;
153        } else {
154            return hostName;
155        }
156    }
157
158    /**
159     * Generate a unique ID - that is friendly for a URL or file system
160     *
161     * @return a unique id
162     */
163    public String generateSanitizedId() {
164        String result = generateId();
165        result = result.replace(':', '-');
166        result = result.replace('_', '-');
167        result = result.replace('.', '-');
168        return result;
169    }
170
171    /**
172     * From a generated id - return the seed (i.e. minus the count)
173     *
174     * @param id the generated identifer
175     * @return the seed
176     */
177    public static String getSeedFromId(String id) {
178        String result = id;
179        if (id != null) {
180            int index = id.lastIndexOf(':');
181            if (index > 0 && (index + 1) < id.length()) {
182                result = id.substring(0, index);
183            }
184        }
185        return result;
186    }
187
188    /**
189     * From a generated id - return the generator count
190     *
191     * @param id
192     * @return the count
193     */
194    public static long getSequenceFromId(String id) {
195        long result = -1;
196        if (id != null) {
197            int index = id.lastIndexOf(':');
198
199            if (index > 0 && (index + 1) < id.length()) {
200                String numStr = id.substring(index + 1, id.length());
201                result = Long.parseLong(numStr);
202            }
203        }
204        return result;
205    }
206
207    /**
208     * Does a proper compare on the ids
209     *
210     * @param id1
211     * @param id2
212     * @return 0 if equal else a positive if id1 is > id2 ...
213     */
214    public static int compare(String id1, String id2) {
215        int result = -1;
216        String seed1 = IdGenerator.getSeedFromId(id1);
217        String seed2 = IdGenerator.getSeedFromId(id2);
218        if (seed1 != null && seed2 != null) {
219            result = seed1.compareTo(seed2);
220            if (result == 0) {
221                long count1 = IdGenerator.getSequenceFromId(id1);
222                long count2 = IdGenerator.getSequenceFromId(id2);
223                result = (int)(count1 - count2);
224            }
225        }
226        return result;
227
228    }
229
230}