| 1 |
package com.paymentlink.service; |
| 2 |
|
| 3 |
import com.github.benmanes.caffeine.cache.Cache; |
| 4 |
import com.github.benmanes.caffeine.cache.Caffeine; |
| 5 |
import com.paymentlink.model.entity.Region; |
| 6 |
import com.paymentlink.repository.RegionRepository; |
| 7 |
import jakarta.annotation.PostConstruct; |
| 8 |
import org.slf4j.Logger; |
| 9 |
import org.slf4j.LoggerFactory; |
| 10 |
import org.springframework.beans.factory.annotation.Value; |
| 11 |
import org.springframework.stereotype.Service; |
| 12 |
import org.springframework.transaction.annotation.Transactional; |
| 13 |
|
| 14 |
import java.util.*; |
| 15 |
import java.util.concurrent.TimeUnit; |
| 16 |
|
| 17 |
@Service |
| 18 |
public class RegionService { |
| 19 |
|
| 20 |
private static final Logger logger = LoggerFactory.getLogger(RegionService.class); |
| 21 |
|
| 22 |
private final RegionRepository regionRepository; |
| 23 |
private Cache<String, Region> regionCache; |
| 24 |
private Cache<String, List<Region>> enabledRegionsCache; |
| 25 |
|
| 26 |
@Value("${region.cache.ttl:600000}") |
| 27 |
private long cacheTtl; |
| 28 |
|
| 29 |
@Value("${region.default.enabled:false}") |
| 30 |
private boolean defaultEnabled; |
| 31 |
|
| 32 |
public RegionService(RegionRepository regionRepository) { |
| 33 |
this.regionRepository = regionRepository; |
| 34 |
} |
| 35 |
|
| 36 |
@PostConstruct |
| 37 |
public void init() { |
| 38 |
|
| 39 |
regionCache = Caffeine.newBuilder() |
| 40 |
.maximumSize(200) |
| 41 |
.expireAfterWrite(cacheTtl, TimeUnit.MILLISECONDS) |
| 42 |
.build(); |
| 43 |
|
| 44 |
enabledRegionsCache = Caffeine.newBuilder() |
| 45 |
.maximumSize(1) |
| 46 |
.expireAfterWrite(cacheTtl, TimeUnit.MILLISECONDS) |
| 47 |
.build(); |
| 48 |
|
| 49 |
logger.info("RegionService initialized with cache TTL: {}ms", cacheTtl); |
| 50 |
} |
| 51 |
|
| 52 |
|
| 53 |
* Check if country is enabled |
| 54 |
*/ |
| 55 |
public boolean isCountryEnabled(String countryCode) { |
| 56 |
Region region = getRegion(countryCode); |
| 57 |
return region != null && region.getEnabled(); |
| 58 |
} |
| 59 |
|
| 60 |
|
| 61 |
* Get region by country code |
| 62 |
*/ |
| 63 |
public Region getRegion(String countryCode) { |
| 64 |
if (countryCode == null || countryCode.isEmpty()) { |
| 65 |
return null; |
| 66 |
} |
| 67 |
|
| 68 |
|
| 69 |
Region cached = regionCache.getIfPresent(countryCode); |
| 70 |
if (cached != null) { |
| 71 |
return cached; |
| 72 |
} |
| 73 |
|
| 74 |
|
| 75 |
Optional<Region> regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase()); |
| 76 |
if (regionOpt.isPresent()) { |
| 77 |
Region region = regionOpt.get(); |
| 78 |
regionCache.put(countryCode, region); |
| 79 |
return region; |
| 80 |
} |
| 81 |
|
| 82 |
return null; |
| 83 |
} |
| 84 |
|
| 85 |
|
| 86 |
* Get all enabled regions |
| 87 |
*/ |
| 88 |
public List<Region> getAllEnabledRegions() { |
| 89 |
|
| 90 |
List<Region> cached = enabledRegionsCache.getIfPresent("enabled"); |
| 91 |
if (cached != null) { |
| 92 |
return cached; |
| 93 |
} |
| 94 |
|
| 95 |
|
| 96 |
List<Region> enabled = regionRepository.findAllEnabled(); |
| 97 |
enabledRegionsCache.put("enabled", enabled); |
| 98 |
return enabled; |
| 99 |
} |
| 100 |
|
| 101 |
|
| 102 |
* Get all regions |
| 103 |
*/ |
| 104 |
public List<Region> getAllRegions() { |
| 105 |
return regionRepository.findAll(); |
| 106 |
} |
| 107 |
|
| 108 |
|
| 109 |
* Enable a country |
| 110 |
*/ |
| 111 |
@Transactional |
| 112 |
public void enableCountry(String countryCode) { |
| 113 |
Optional<Region> regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase()); |
| 114 |
if (regionOpt.isPresent()) { |
| 115 |
Region region = regionOpt.get(); |
| 116 |
region.setEnabled(true); |
| 117 |
regionRepository.save(region); |
| 118 |
|
| 119 |
|
| 120 |
regionCache.invalidate(countryCode); |
| 121 |
enabledRegionsCache.invalidateAll(); |
| 122 |
|
| 123 |
logger.info("Enabled country: {}", countryCode); |
| 124 |
} else { |
| 125 |
logger.warn("Country not found: {}", countryCode); |
| 126 |
} |
| 127 |
} |
| 128 |
|
| 129 |
|
| 130 |
* Disable a country |
| 131 |
*/ |
| 132 |
@Transactional |
| 133 |
public void disableCountry(String countryCode) { |
| 134 |
Optional<Region> regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase()); |
| 135 |
if (regionOpt.isPresent()) { |
| 136 |
Region region = regionOpt.get(); |
| 137 |
region.setEnabled(false); |
| 138 |
regionRepository.save(region); |
| 139 |
|
| 140 |
|
| 141 |
regionCache.invalidate(countryCode); |
| 142 |
enabledRegionsCache.invalidateAll(); |
| 143 |
|
| 144 |
logger.info("Disabled country: {}", countryCode); |
| 145 |
} else { |
| 146 |
logger.warn("Country not found: {}", countryCode); |
| 147 |
} |
| 148 |
} |
| 149 |
|
| 150 |
|
| 151 |
* Get statistics |
| 152 |
*/ |
| 153 |
public Map<String, Object> getStatistics() { |
| 154 |
long total = regionRepository.count(); |
| 155 |
long enabled = regionRepository.countEnabled(); |
| 156 |
|
| 157 |
Map<String, Object> stats = new HashMap<>(); |
| 158 |
stats.put("total", total); |
| 159 |
stats.put("enabled", enabled); |
| 160 |
stats.put("disabled", total - enabled); |
| 161 |
|
| 162 |
return stats; |
| 163 |
} |
| 164 |
|
| 165 |
|
| 166 |
* Initialize default regions with country data |
| 167 |
* This method is idempotent and should be called on application startup |
| 168 |
*/ |
| 169 |
@Transactional |
| 170 |
public void initializeDefaultRegions() { |
| 171 |
long existingCount = regionRepository.count(); |
| 172 |
if (existingCount > 0) { |
| 173 |
logger.info("Regions table already populated with {} entries", existingCount); |
| 174 |
return; |
| 175 |
} |
| 176 |
|
| 177 |
logger.info("Initializing default regions..."); |
| 178 |
List<Region> regions = getDefaultRegionData(); |
| 179 |
regionRepository.saveAll(regions); |
| 180 |
logger.info("Initialized {} regions", regions.size()); |
| 181 |
} |
| 182 |
|
| 183 |
|
| 184 |
* Get default region data for major countries |
| 185 |
* In production, this would be loaded from a JSON file or database |
| 186 |
*/ |
| 187 |
private List<Region> getDefaultRegionData() { |
| 188 |
List<Region> regions = new ArrayList<>(); |
| 189 |
|
| 190 |
|
| 191 |
Set<String> enabledByDefault = Set.of("US", "CA", "GB", "FR", "DE", "ES"); |
| 192 |
|
| 193 |
|
| 194 |
regions.add(createRegion("US", "United States", "en", "USD", enabledByDefault.contains("US"))); |
| 195 |
regions.add(createRegion("CA", "Canada", "en", "CAD", enabledByDefault.contains("CA"))); |
| 196 |
regions.add(createRegion("GB", "United Kingdom", "en", "GBP", enabledByDefault.contains("GB"))); |
| 197 |
regions.add(createRegion("FR", "France", "fr", "EUR", enabledByDefault.contains("FR"))); |
| 198 |
regions.add(createRegion("DE", "Germany", "de", "EUR", enabledByDefault.contains("DE"))); |
| 199 |
regions.add(createRegion("ES", "Spain", "es", "EUR", enabledByDefault.contains("ES"))); |
| 200 |
regions.add(createRegion("IT", "Italy", "it", "EUR", false)); |
| 201 |
regions.add(createRegion("MX", "Mexico", "es", "MXN", false)); |
| 202 |
regions.add(createRegion("BR", "Brazil", "pt", "BRL", false)); |
| 203 |
regions.add(createRegion("AR", "Argentina", "es", "ARS", false)); |
| 204 |
regions.add(createRegion("JP", "Japan", "ja", "JPY", false)); |
| 205 |
regions.add(createRegion("CN", "China", "zh", "CNY", false)); |
| 206 |
regions.add(createRegion("IN", "India", "en", "INR", false)); |
| 207 |
regions.add(createRegion("AU", "Australia", "en", "AUD", false)); |
| 208 |
regions.add(createRegion("NZ", "New Zealand", "en", "NZD", false)); |
| 209 |
regions.add(createRegion("SG", "Singapore", "en", "SGD", false)); |
| 210 |
regions.add(createRegion("HK", "Hong Kong", "en", "HKD", false)); |
| 211 |
regions.add(createRegion("KR", "South Korea", "ko", "KRW", false)); |
| 212 |
regions.add(createRegion("SE", "Sweden", "sv", "SEK", false)); |
| 213 |
regions.add(createRegion("NO", "Norway", "no", "NOK", false)); |
| 214 |
regions.add(createRegion("DK", "Denmark", "da", "DKK", false)); |
| 215 |
regions.add(createRegion("CH", "Switzerland", "de", "CHF", false)); |
| 216 |
regions.add(createRegion("NL", "Netherlands", "nl", "EUR", false)); |
| 217 |
regions.add(createRegion("BE", "Belgium", "nl", "EUR", false)); |
| 218 |
regions.add(createRegion("AT", "Austria", "de", "EUR", false)); |
| 219 |
regions.add(createRegion("PL", "Poland", "pl", "PLN", false)); |
| 220 |
regions.add(createRegion("PT", "Portugal", "pt", "EUR", false)); |
| 221 |
regions.add(createRegion("IE", "Ireland", "en", "EUR", false)); |
| 222 |
regions.add(createRegion("TR", "Turkey", "tr", "TRY", false)); |
| 223 |
regions.add(createRegion("ZA", "South Africa", "en", "ZAR", false)); |
| 224 |
|
| 225 |
return regions; |
| 226 |
} |
| 227 |
|
| 228 |
private Region createRegion(String code, String name, String lang, String currency, boolean enabled) { |
| 229 |
return Region.builder() |
| 230 |
.countryCode(code) |
| 231 |
.countryName(name) |
| 232 |
.languageCode(lang) |
| 233 |
.currencyCode(currency) |
| 234 |
.enabled(enabled) |
| 235 |
.build(); |
| 236 |
} |
| 237 |
|
| 238 |
|
| 239 |
* Clear all caches |
| 240 |
*/ |
| 241 |
public void clearCache() { |
| 242 |
regionCache.invalidateAll(); |
| 243 |
enabledRegionsCache.invalidateAll(); |
| 244 |
logger.info("Region caches cleared"); |
| 245 |
} |
| 246 |
} |
| 247 |
|