001package com.killcoding.datasource; 002 003import java.io.File; 004import com.killcoding.log.Logger; 005import com.killcoding.log.LoggerFactory; 006import com.killcoding.datasource.DriverDataSource; 007import com.killcoding.tool.ConfigProperties; 008import com.killcoding.tool.CommonTools; 009import java.sql.Connection; 010import java.sql.SQLException; 011import com.killcoding.datasource.CacheDriverExecutor; 012import java.util.Map; 013import java.util.HashMap; 014import com.killcoding.datasource.Clock; 015import java.sql.Timestamp; 016import java.util.concurrent.ConcurrentHashMap; 017 018/** 019 * 020 CREATE TABLE identity_card ( 021 id CHAR(16), 022 identity_name VARCHAR(200), 023 identity_value BIGINT, 024 generate_task_id CHAR(16), 025 created_user_id CHAR(16), 026 updated_user_id CHAR(16), 027 deleted_user_id CHAR(16), 028 created_at DATETIME(6), 029 updated_at DATETIME(6), 030 deleted_at DATETIME(6), 031 deleted TINYINT(1) 032 ) ENGINE=InnoDB; 033 034 ALTER TABLE identity_card ADD PRIMARY KEY (id); 035 ALTER TABLE identity_card ADD UNIQUE INDEX idx_idcard_unique_idname (identity_name); 036 ALTER TABLE identity_card ADD INDEX idx_idcard_deleted (deleted); 037 * 038 * */ 039public final class IdentityCard { 040 041 private final static Map<String, Integer> CURRENT_VALUES = new ConcurrentHashMap<String, Integer>(); 042 private final static Map<String, Long> STEP_LAST_VALUES = new ConcurrentHashMap<String, Long>(); 043 044 private static final String SQL_INSERT = "INSERT INTO %s (id,identity_name,identity_value,generate_task_id,created_user_id,updated_user_id,deleted_user_id,created_at,updated_at,deleted_at,deleted) VALUES (:id,:identity_name,:identity_value,:generate_task_id,:created_user_id,:updated_user_id,NULL,:created_at,:updated_at,:deleted_at,:deleted)"; 045 046 private static final String SQL_UPDATE = "UPDATE %s SET identity_value = identity_value + :step_value,generate_task_id = :generate_task_id,updated_user_id = :updated_user_id,updated_at = :updated_at WHERE id = :id AND (generate_task_id = '0' OR updated_at <= :timeout_updated_at)"; 047 048 private static final String SQL_RESET = "UPDATE %s SET identity_value = :identity_value,updated_user_id = :updated_user_id,updated_at = :updated_at WHERE id = :id AND generate_task_id = :generate_task_id"; 049 050 private static final String SQL_RESET_FORCE = "UPDATE %s SET identity_value = :identity_value,updated_user_id = :updated_user_id,updated_at = :updated_at WHERE id = :id"; 051 052 private static final String SQL_CLEAR = "UPDATE %s SET generate_task_id = '0',updated_user_id = :updated_user_id,updated_at = :updated_at WHERE id = :id AND generate_task_id = :generate_task_id"; 053 054 private static final String SQL_FIRST = "SELECT * FROM %s WHERE deleted = 0 AND identity_name = :identity_name"; 055 056 private static final String SQL_FIRST_BY_GENERATE_TASK_ID = "SELECT * FROM %s WHERE deleted = 0 AND identity_name = :identity_name AND generate_task_id = :generate_task_id"; 057 058 public static File APP_IDENTITY_CARD = null; 059 060 private Logger log = LoggerFactory.getLogger(IdentityCard.class); 061 062 private CacheDriverExecutor driverExecutor = null; 063 064 private static Long MAX_VALUE_LIMITED = Long.MAX_VALUE - 1; 065 private static Long MIN_VALUE_LIMITED = 1L; 066 private static Integer STEP_VALUE_LIMITED = 0; 067 068 private static boolean enable = false; 069 private static ConfigProperties configProperties = null; 070 private static DriverDataSource dataSource = null; 071 private static String dataSourcePath = null; 072 private static String tableName = null; 073 private static Long timeout = 10000L; 074 private static Long maxValue = MAX_VALUE_LIMITED; 075 private static Long minValue = MIN_VALUE_LIMITED; 076 private static Integer stepValue = STEP_VALUE_LIMITED; 077 078 private String identityName = null; 079 private String generateTaskId = null; 080 private Object userId = null; 081 082 public IdentityCard(String identityName) { 083 super(); 084 this.identityName = identityName; 085 initValues(); 086 init(); 087 } 088 089 private synchronized void initValues(){ 090 if(!CURRENT_VALUES.containsKey(identityName)){ 091 CURRENT_VALUES.put(identityName,0); 092 STEP_LAST_VALUES.put(identityName,0L); 093 } 094 } 095 096 private void initConn() throws SQLException { 097 Connection conn = dataSource.getConnection(); 098 conn.setAutoCommit(false); 099 driverExecutor = new CacheDriverExecutor(conn); 100 } 101 102 public synchronized Long generateId() throws SQLException { 103 if (!enable) 104 return null; 105 106 if(CURRENT_VALUES.get(identityName) <= 0){ 107 STEP_LAST_VALUES.put(identityName,generateIdForNextStep()); 108 CURRENT_VALUES.put(identityName,stepValue); 109 } 110 111 Long val = STEP_LAST_VALUES.get(identityName) - CURRENT_VALUES.get(identityName); 112 CURRENT_VALUES.put(identityName,CURRENT_VALUES.get(identityName) - 1); 113 return val; 114 } 115 116 public synchronized Long generateIdForNextStep() throws SQLException { 117 118 if (!enable) 119 return null; 120 121 Long nextValue = null; 122 try { 123 initConn(); 124 Object id = null; 125 generateTaskId = CommonTools.generateId(16); 126 Map<String, Object> item = first(); 127 if (item == null) { 128 id = create(); 129 item = first(); 130 } 131 id = item.get("id"); 132 boolean updated = update(id); 133 if (updated) { 134 item = firstByGenerateTaskId(); 135 nextValue = Long.parseLong(item.get("identity_value").toString()); 136 } 137 if (nextValue != null) { 138 if (nextValue > maxValue) { 139 nextValue = minValue + stepValue; 140 reset(id,nextValue); 141 } 142 } 143 clear(id); 144 driverExecutor.commit(); 145 if (nextValue == null) { 146 int errorCode = 99800; 147 String errorMsg = String.format("The '%s' next value is null.", identityName); 148 log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg)); 149 throw new SQLException(errorMsg) { 150 @Override 151 public int getErrorCode() { 152 return errorCode; 153 } 154 }; 155 } 156 return nextValue; 157 } catch (Exception e) { 158 log.warn(e.getMessage(), e); 159 160 if (driverExecutor != null) 161 driverExecutor.rollback(); 162 163 int errorCode = 99802; 164 String errorMsg = e.getMessage(); 165 log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg)); 166 throw new SQLException(errorMsg) { 167 @Override 168 public int getErrorCode() { 169 return errorCode; 170 } 171 }; 172 173 } finally { 174 if (driverExecutor != null){ 175 driverExecutor.close(); 176 } 177 } 178 } 179 180 public synchronized String generateId(int length) throws SQLException { 181 if (!enable) 182 return null; 183 184 if(length < 1){ 185 int errorCode = 99801; 186 String errorMsg = String.format("The value length '%s' cannot be less than 1.", length); 187 log.warn(String.format("ERROR CODE: (%s) %s", errorCode, errorMsg)); 188 throw new SQLException(errorMsg) { 189 @Override 190 public int getErrorCode() { 191 return errorCode; 192 } 193 }; 194 } 195 196 Long val = generateId(); 197 if(val > getMaxValueForLength(length)){ 198 Map<String,Object> item = first(); 199 Object id = item.get("id"); 200 resetForce(id); 201 val = minValue; 202 } 203 if (val == null) 204 return null; 205 206 return String.format("%0" + length + "d", val); 207 } 208 209 public synchronized String generateId(int length, String prefix) throws SQLException { 210 if (!enable) 211 return null; 212 213 int valLen = length - prefix.length(); 214 return String.format("%s%s", prefix, generateId(valLen)); 215 } 216 217 public synchronized String generateId(int length, String prefix, String delimiter,int randomSuffixLength) throws SQLException { 218 if (!enable) 219 return null; 220 221 if(CommonTools.isBlank(delimiter)){ 222 delimiter = ""; 223 } 224 String suffix = ""; 225 if(randomSuffixLength > 0){ 226 suffix = String.format("%s%s",delimiter,CommonTools.generateId(randomSuffixLength).toUpperCase()); 227 } 228 int valLen = length - prefix.length() - delimiter.length() - suffix.length(); 229 return String.format("%s%s%s%s", prefix,delimiter,generateId(valLen),suffix).substring(0,length); 230 } 231 232 private synchronized void init() { 233 if (dataSource == null) { 234 try { 235 if (APP_IDENTITY_CARD == null) { 236 APP_IDENTITY_CARD = CommonTools.getSystemProperties(IdentityCard.class, "APP_IDENTITY_CARD", 237 "IdentityCard.properties"); 238 } 239 if (APP_IDENTITY_CARD != null && APP_IDENTITY_CARD.exists() && configProperties == null) { 240 configProperties = new ConfigProperties(); 241 Runnable updatedRun = new Runnable() { 242 @Override 243 public void run() { 244 try { 245 reload(); 246 Logger.systemMark(IdentityCard.class, "IdentityCard Reloaded"); 247 } catch (Exception e) { 248 log.error(e.getMessage(), e); 249 } 250 } 251 }; 252 253 configProperties.load(APP_IDENTITY_CARD, updatedRun); 254 enable = configProperties.getBoolean("Enable", false); 255 tableName = configProperties.getString("TableName", "identity_card"); 256 timeout = configProperties.getMilliSeconds("Timeout", 10000L); 257 maxValue = configProperties.getLong("MaxValue", MAX_VALUE_LIMITED); 258 minValue = configProperties.getLong("MinValue", MIN_VALUE_LIMITED); 259 stepValue = configProperties.getInteger("StepValue", STEP_VALUE_LIMITED); 260 261 if(maxValue > MAX_VALUE_LIMITED) maxValue = MAX_VALUE_LIMITED; 262 263 if(minValue < MIN_VALUE_LIMITED) minValue = MIN_VALUE_LIMITED; 264 265 if(stepValue < STEP_VALUE_LIMITED) stepValue = STEP_VALUE_LIMITED; 266 267 if (enable) { 268 dataSourcePath = configProperties.getString("DataSource"); 269 if (!CommonTools.isBlank(dataSourcePath)) { 270 dataSource = new DriverDataSource(new File(dataSourcePath)); 271 } 272 } 273 } 274 } catch (Exception e) { 275 log.error(e.getMessage(), e); 276 } 277 } 278 } 279 280 private void reload() { 281 if (configProperties != null) { 282 enable = configProperties.getBoolean("Enable", false); 283 tableName = configProperties.getString("TableName", "identity_card"); 284 timeout = configProperties.getMilliSeconds("Timeout", 10000L); 285 String reloadDataSourcePath = configProperties.getString("DataSource"); 286 287 maxValue = configProperties.getLong("MaxValue", MAX_VALUE_LIMITED); 288 minValue = configProperties.getLong("MinValue", MIN_VALUE_LIMITED); 289 stepValue = configProperties.getInteger("StepValue", STEP_VALUE_LIMITED); 290 291 if(maxValue > MAX_VALUE_LIMITED) maxValue = MAX_VALUE_LIMITED; 292 293 if(minValue < MIN_VALUE_LIMITED) minValue = MIN_VALUE_LIMITED; 294 295 if(stepValue < STEP_VALUE_LIMITED) stepValue = STEP_VALUE_LIMITED; 296 297 boolean isReload = false; 298 if (!CommonTools.isBlank(reloadDataSourcePath)) { 299 isReload = !dataSourcePath.equals(reloadDataSourcePath); 300 } 301 if (dataSource != null && isReload) { 302 // May be referenced when expansion is needed 303 // dataSource.closeAll(); 304 dataSourcePath = reloadDataSourcePath; 305 } 306 // May be referenced when expansion is needed 307 // if (!enable && dataSource != null) { 308 // dataSource.closeAll(); 309 // } 310 if (enable && isReload) { 311 if (!CommonTools.isBlank(dataSourcePath)) { 312 dataSource = new DriverDataSource(new File(dataSourcePath)); 313 } 314 } 315 } 316 } 317 318 private boolean update(Object id) throws SQLException { 319 Clock clock = new Clock(); 320 Timestamp currentTime = clock.getSqlTimestamp(); 321 String sql = formatSql(SQL_UPDATE); 322 Map<String, Object> params = new HashMap<String, Object>(); 323 params.put("id", id); 324 params.put("step_value",stepValue); 325 params.put("generate_task_id", generateTaskId); 326 params.put("updated_user_id", userId); 327 params.put("updated_at", currentTime); 328 params.put("timeout_updated_at", new Timestamp(currentTime.getTime() - timeout)); 329 return driverExecutor.execute(sql, params) > 0; 330 } 331 332 private void clear(Object id) throws SQLException { 333 Clock clock = new Clock(); 334 Timestamp currentTime = clock.getSqlTimestamp(); 335 String sql = formatSql(SQL_CLEAR); 336 Map<String, Object> params = new HashMap<String, Object>(); 337 params.put("id", id); 338 params.put("generate_task_id", generateTaskId); 339 params.put("updated_user_id", userId); 340 params.put("updated_at", currentTime); 341 driverExecutor.execute(sql, params); 342 } 343 344 private void reset(Object id,Long resetValue) throws SQLException { 345 Clock clock = new Clock(); 346 Timestamp currentTime = clock.getSqlTimestamp(); 347 String sql = formatSql(SQL_RESET); 348 Map<String, Object> params = new HashMap<String, Object>(); 349 params.put("id", id); 350 params.put("identity_value", resetValue); 351 params.put("generate_task_id", generateTaskId); 352 params.put("updated_user_id", userId); 353 params.put("updated_at", currentTime); 354 driverExecutor.execute(sql, params); 355 } 356 357 private void resetForce(Object id) throws SQLException { 358 Clock clock = new Clock(); 359 Timestamp currentTime = clock.getSqlTimestamp(); 360 String sql = formatSql(SQL_RESET_FORCE); 361 Map<String, Object> params = new HashMap<String, Object>(); 362 params.put("id", id); 363 params.put("identity_value", minValue); 364 params.put("updated_user_id", userId); 365 params.put("updated_at", currentTime); 366 driverExecutor.execute(sql, params); 367 } 368 369 private Object create() throws SQLException { 370 Object id = CommonTools.generateId(16); 371 Clock clock = new Clock(); 372 Timestamp currentTime = clock.getSqlTimestamp(); 373 Long createIdentityValue = minValue; 374 375 if(createIdentityValue < 0) createIdentityValue = 0L; 376 377 String sql = formatSql(SQL_INSERT); 378 Map<String, Object> params = new HashMap<String, Object>(); 379 params.put("id", id); 380 params.put("identity_name", identityName); 381 params.put("identity_value", createIdentityValue); 382 params.put("generate_task_id", "0"); 383 params.put("created_user_id", userId); 384 params.put("updated_user_id", userId); 385 params.put("deleted_user_id", null); 386 params.put("created_at", currentTime); 387 params.put("updated_at", currentTime); 388 params.put("deleted_at", null); 389 params.put("deleted", 0); 390 driverExecutor.execute(sql, params); 391 return id; 392 } 393 394 private Map<String, Object> first() throws SQLException { 395 String sql = formatSql(SQL_FIRST); 396 Map<String, Object> params = new HashMap<String, Object>(); 397 params.put("identity_name", identityName); 398 return driverExecutor.first(sql, params); 399 } 400 401 private Map<String, Object> firstByGenerateTaskId() throws SQLException { 402 String sql = formatSql(SQL_FIRST_BY_GENERATE_TASK_ID); 403 Map<String, Object> params = new HashMap<String, Object>(); 404 params.put("identity_name", identityName); 405 params.put("generate_task_id", generateTaskId); 406 return driverExecutor.first(sql, params); 407 } 408 409 private Long getMaxValueForLength(int length) { 410 String strValue = String.format("%0" + length + "d",0).replaceAll("0","9"); 411 return Long.parseLong(strValue); 412 } 413 414 public static DriverDataSource getDataSource() { 415 return dataSource; 416 } 417 418 public String formatSql(String sql) { 419 return String.format(sql, tableName); 420 } 421 422 public String formatSql(String sql,Integer stepValue) { 423 return String.format(sql, tableName,stepValue); 424 } 425 426 public void setUserId(Object userId) { 427 this.userId = userId; 428 } 429 430 public Object getUserId() { 431 return this.userId; 432 } 433 434 public String getIdentityName() { 435 return identityName; 436 } 437 438 public static ConfigProperties getConfigProperties(){ 439 return configProperties; 440 } 441}