001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.util.server.persistence; 018 019import com.echothree.util.common.exception.PersistenceDatabaseException; 020import java.sql.Connection; 021import java.sql.PreparedStatement; 022import java.sql.ResultSet; 023import java.sql.SQLException; 024import java.util.concurrent.locks.ReentrantLock; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028public class EntityIdGenerator { 029 030 private static final Logger log = LoggerFactory.getLogger(EntityIdGenerator.class); 031 032 static private final String selectQuery = "SELECT eid_lastentityid FROM entityids WHERE eid_componentvendorname = ? AND eid_entitytypename = ?"; 033 static private final String updateQuery = "UPDATE entityids SET eid_lastentityid = ? WHERE eid_componentvendorname = ? AND eid_entitytypename = ? AND eid_lastentityid = ?"; 034 static private final String insertQuery = "INSERT INTO entityids (eid_componentvendorname, eid_entitytypename, eid_lastentityid) VALUES (?, ?, ?)"; 035 036 static private final int defaultChunkSize = 100; 037 static private final int retryCount = 5; 038 039 private final ReentrantLock lock = new ReentrantLock(true); 040 041 private String componentVendorName; 042 private String entityTypeName; 043 private int chunkSize; 044 private long guardedValue = 0; 045 private long currentValue = 0; 046 047 private void init(String componentVendorName, String entityTypeName, int chunkSize) { 048 log.debug("EntityIdGenerator(componentVendorName = {}, entityTypeName = {}, chunkSize = {})", 049 componentVendorName, entityTypeName, chunkSize); 050 051 this.componentVendorName = componentVendorName; 052 this.entityTypeName = entityTypeName; 053 this.chunkSize = chunkSize; 054 } 055 056 /** Creates a new instance of EntityIdGenerator */ 057 public EntityIdGenerator(String componentVendorName, String entityTypeName, int chunkSize) { 058 init(componentVendorName, entityTypeName, chunkSize); 059 } 060 061 /** Creates a new instance of EntityIdGenerator */ 062 public EntityIdGenerator(String componentVendorName, String entityTypeName) { 063 init(componentVendorName, entityTypeName, defaultChunkSize); 064 } 065 066 public Connection getConnection() { 067 return DslContextFactory.getInstance().getNTDslContext().parsingConnection(); 068 } 069 070 private boolean getNewChunk() 071 throws SQLException { 072 log.debug("getNewChunk(), componentVendorName = {}, entityTypeName = {}", 073 componentVendorName, entityTypeName); 074 075 PreparedStatement ps = null; 076 ResultSet rs = null; 077 long currentMax; 078 079 try(var conn = getConnection();) { 080 try { 081 ps = conn.prepareStatement(selectQuery); 082 ps.setString(1, componentVendorName); 083 ps.setString(2, entityTypeName); 084 rs = ps.executeQuery(); 085 086 if(rs.next()) { 087 currentMax = rs.getLong(1); 088 089 rs.close(); 090 rs = null; 091 092 ps.close(); 093 ps = null; 094 095 ps = conn.prepareStatement(updateQuery); 096 ps.setLong(1, currentMax + chunkSize); 097 ps.setString(2, componentVendorName); 098 ps.setString(3, entityTypeName); 099 ps.setLong(4, currentMax); 100 101 if(ps.executeUpdate() != 1) 102 return false; 103 else { 104 guardedValue = currentMax + chunkSize; 105 currentValue = currentMax; 106 return true; 107 } 108 } else { 109 rs.close(); 110 rs = null; 111 112 ps.close(); 113 ps = null; 114 115 ps = conn.prepareStatement(insertQuery); 116 ps.setString(1, componentVendorName); 117 ps.setString(2, entityTypeName); 118 ps.setLong(3, chunkSize); 119 120 if(ps.executeUpdate() != 1) { 121 return false; 122 } else { 123 guardedValue = chunkSize; 124 currentValue = 0; 125 return true; 126 } 127 } 128 } finally { 129 if(rs != null) { 130 rs.close(); 131 } 132 133 if(ps != null) { 134 ps.close(); 135 } 136 137 if(conn != null) { 138 conn.close(); 139 } 140 } 141 } 142 } 143 144 public long getNextEntityId() { 145 long nextEntityId; 146 147 lock.lock(); 148 try { 149 if(currentValue == guardedValue) { 150 try { 151 for(var iter = 0; iter <= retryCount; iter++) { 152 if(getNewChunk()) 153 break; 154 } 155 } catch (SQLException se) { 156 throw new PersistenceDatabaseException(se); 157 } 158 } 159 160 if(currentValue == guardedValue) { 161 throw new PersistenceDatabaseException("Could not get next increment value"); 162 } 163 164 nextEntityId = ++currentValue; 165 } finally { 166 lock.unlock(); 167 } 168 169 log.debug("getNextEntityId(), nextEntityId = {}, componentVendorName = {}, entityTypeName = {}", 170 nextEntityId, componentVendorName, entityTypeName); 171 172 return nextEntityId; 173 } 174 175}