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