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}