001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 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; 025 026public class EntityIdGenerator { 027 028 static private final String selectQuery = "SELECT eid_lastentityid FROM entityids WHERE eid_componentvendorname = ? AND eid_entitytypename = ?"; 029 static private final String updateQuery = "UPDATE entityids SET eid_lastentityid = ? WHERE eid_componentvendorname = ? AND eid_entitytypename = ? AND eid_lastentityid = ?"; 030 static private final String insertQuery = "INSERT INTO entityids (eid_componentvendorname, eid_entitytypename, eid_lastentityid) VALUES (?, ?, ?)"; 031 032 static private final int defaultChunkSize = 100; 033 static private final int retryCount = 5; 034 035 private final ReentrantLock lock = new ReentrantLock(true); 036 037 private String componentVendorName; 038 private String entityTypeName; 039 private int chunkSize; 040 private long guardedValue = 0; 041 private long currentValue = 0; 042 043 private void init(String componentVendorName, String entityTypeName, int chunkSize) { 044 this.componentVendorName = componentVendorName; 045 this.entityTypeName = entityTypeName; 046 this.chunkSize = chunkSize; 047 } 048 049 /** Creates a new instance of EntityIdGenerator */ 050 public EntityIdGenerator(String componentVendorName, String entityTypeName, int chunkSize) { 051 init(componentVendorName, entityTypeName, chunkSize); 052 } 053 054 /** Creates a new instance of EntityIdGenerator */ 055 public EntityIdGenerator(String componentVendorName, String entityTypeName) { 056 init(componentVendorName, entityTypeName, defaultChunkSize); 057 } 058 059 public Connection getConnection() { 060 return DslContextFactory.getInstance().getNTDslContext().parsingConnection(); 061 } 062 063 private boolean getNewChunk() 064 throws SQLException { 065 PreparedStatement ps = null; 066 ResultSet rs = null; 067 long currentMax; 068 069 try(var conn = getConnection();) { 070 try { 071 ps = conn.prepareStatement(selectQuery); 072 ps.setString(1, componentVendorName); 073 ps.setString(2, entityTypeName); 074 rs = ps.executeQuery(); 075 076 if(rs.next()) { 077 currentMax = rs.getLong(1); 078 079 rs.close(); 080 rs = null; 081 082 ps.close(); 083 ps = null; 084 085 ps = conn.prepareStatement(updateQuery); 086 ps.setLong(1, currentMax + chunkSize); 087 ps.setString(2, componentVendorName); 088 ps.setString(3, entityTypeName); 089 ps.setLong(4, currentMax); 090 091 if(ps.executeUpdate() != 1) 092 return false; 093 else { 094 guardedValue = currentMax + chunkSize; 095 currentValue = currentMax; 096 return true; 097 } 098 } else { 099 rs.close(); 100 rs = null; 101 102 ps.close(); 103 ps = null; 104 105 ps = conn.prepareStatement(insertQuery); 106 ps.setString(1, componentVendorName); 107 ps.setString(2, entityTypeName); 108 ps.setLong(3, chunkSize); 109 110 if(ps.executeUpdate() != 1) { 111 return false; 112 } else { 113 guardedValue = chunkSize; 114 currentValue = 0; 115 return true; 116 } 117 } 118 } finally { 119 if(rs != null) { 120 rs.close(); 121 } 122 123 if(ps != null) { 124 ps.close(); 125 } 126 127 if(conn != null) { 128 conn.close(); 129 } 130 } 131 } 132 } 133 134 public long getNextEntityId() { 135 lock.lock(); 136 137 try { 138 if(currentValue == guardedValue) { 139 try { 140 for(var iter = 0; iter <= retryCount; iter++) { 141 if(getNewChunk()) 142 break; 143 } 144 } catch (SQLException se) { 145 throw new PersistenceDatabaseException(se); 146 } 147 } 148 149 if(currentValue == guardedValue) { 150 throw new PersistenceDatabaseException("Could not get next increment value"); 151 } 152 153 return ++currentValue; 154 } finally { 155 lock.unlock(); 156 } 157 } 158 159}