Apereo CAS Core Utilities - A comprehensive utility library providing functional programming constructs, encryption utilities, configuration helpers, and core infrastructure components for the Central Authentication Service framework
—
Additional specialized utilities including cache management, file watching, SSL utilities, JPA converters, concurrency utilities, virtual threading support, and other infrastructure components.
Abstract base class for distributed cache management with pluggable storage backends.
public abstract class BaseDistributedCacheManager<K, V> implements DistributedCacheManager<K, V> {
// Abstract methods for implementation
protected abstract void putInternal(K key, V value, Duration expiration);
protected abstract Optional<V> getInternal(K key);
protected abstract boolean removeInternal(K key);
protected abstract void clearInternal();
// Template methods with common functionality
@Override
public void put(K key, V value);
@Override
public void put(K key, V value, Duration expiration);
@Override
public Optional<V> get(K key);
@Override
public boolean containsKey(K key);
@Override
public boolean remove(K key);
@Override
public void clear();
@Override
public Set<K> keys();
@Override
public long size();
// Batch operations
public Map<K, V> getAll(Collection<K> keys);
public void putAll(Map<K, V> entries, Duration expiration);
public void removeAll(Collection<K> keys);
// Statistics and monitoring
public CacheStatistics getStatistics();
public void resetStatistics();
}Interface for cache managers that support key mapping and transformation operations.
public interface MappableDistributedCacheManager<K, V> extends DistributedCacheManager<K, V> {
// Key transformation operations
<T> MappableDistributedCacheManager<T, V> mapKeys(Function<K, T> keyMapper);
<T> MappableDistributedCacheManager<K, T> mapValues(Function<V, T> valueMapper);
// Filtered views
MappableDistributedCacheManager<K, V> filterKeys(Predicate<K> keyPredicate);
MappableDistributedCacheManager<K, V> filterValues(Predicate<V> valuePredicate);
// Conditional operations
boolean putIfAbsent(K key, V value);
boolean putIfAbsent(K key, V value, Duration expiration);
boolean replace(K key, V oldValue, V newValue);
V computeIfAbsent(K key, Function<K, V> mappingFunction);
V computeIfPresent(K key, BiFunction<K, V, V> remappingFunction);
}Wrapper class for objects stored in distributed cache with metadata and expiration support.
public class DistributedCacheObject<T> implements Serializable {
// Core properties
private final T value;
private final Instant createdAt;
private final Instant expiresAt;
private final Map<String, String> metadata;
// Constructors
public DistributedCacheObject(T value);
public DistributedCacheObject(T value, Duration ttl);
public DistributedCacheObject(T value, Instant expiresAt, Map<String, String> metadata);
// Getters
public T getValue();
public Instant getCreatedAt();
public Instant getExpiresAt();
public Map<String, String> getMetadata();
// Utility methods
public boolean isExpired();
public boolean isExpired(Instant currentTime);
public Duration getTimeToLive();
public Duration getAge();
// Factory methods
public static <T> DistributedCacheObject<T> of(T value);
public static <T> DistributedCacheObject<T> of(T value, Duration ttl);
}Custom distributed cache implementation:
@Component
public class RedisDistributedCacheManager<K, V> extends BaseDistributedCacheManager<K, V> {
private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper objectMapper;
private final String keyPrefix;
public RedisDistributedCacheManager(RedisTemplate<String, Object> redisTemplate,
ObjectMapper objectMapper) {
this.redisTemplate = redisTemplate;
this.objectMapper = objectMapper;
this.keyPrefix = "cas:cache:";
}
@Override
protected void putInternal(K key, V value, Duration expiration) {
try {
String redisKey = keyPrefix + key.toString();
DistributedCacheObject<V> cacheObject = new DistributedCacheObject<>(value, expiration);
String serialized = objectMapper.writeValueAsString(cacheObject);
redisTemplate.opsForValue().set(redisKey, serialized, expiration);
} catch (Exception e) {
log.error("Failed to put value in Redis cache", e);
throw new CacheException("Cache put operation failed", e);
}
}
@Override
protected Optional<V> getInternal(K key) {
try {
String redisKey = keyPrefix + key.toString();
String serialized = (String) redisTemplate.opsForValue().get(redisKey);
if (serialized != null) {
DistributedCacheObject<V> cacheObject = objectMapper.readValue(
serialized,
new TypeReference<DistributedCacheObject<V>>() {}
);
if (!cacheObject.isExpired()) {
return Optional.of(cacheObject.getValue());
} else {
// Remove expired entry
redisTemplate.delete(redisKey);
}
}
return Optional.empty();
} catch (Exception e) {
log.error("Failed to get value from Redis cache", e);
return Optional.empty();
}
}
@Override
protected boolean removeInternal(K key) {
String redisKey = keyPrefix + key.toString();
return Boolean.TRUE.equals(redisTemplate.delete(redisKey));
}
@Override
protected void clearInternal() {
Set<String> keys = redisTemplate.keys(keyPrefix + "*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
}Mappable cache usage:
@Service
public class UserCacheService {
private final MappableDistributedCacheManager<String, User> userCache;
public void cacheUser(User user) {
// Cache by user ID
userCache.put(user.getId(), user, Duration.ofHours(1));
// Create mapped view for email-based lookups
MappableDistributedCacheManager<String, User> emailCache = userCache
.mapKeys(User::getEmail)
.filterValues(u -> u.getEmail() != null);
emailCache.putIfAbsent(user.getEmail(), user, Duration.ofHours(1));
}
public Optional<User> getUserById(String userId) {
return userCache.get(userId);
}
public Optional<User> getActiveUser(String userId) {
// Filtered view for active users only
return userCache
.filterValues(User::isActive)
.get(userId);
}
}Core interface for file system watching with callback-based notifications.
public interface WatcherService {
// Service lifecycle
void start(String name);
void start(String name, Runnable onChange);
void stop();
boolean isRunning();
// Watch operations
void watch(Path file, Runnable onChange);
void watch(Resource resource, Runnable onChange);
void watch(File file, Consumer<WatchEvent<?>> eventConsumer);
// Configuration
void setWatchDelay(long delay, TimeUnit timeUnit);
void setWatchEvents(WatchEvent.Kind<?>... eventTypes);
}File system watcher implementation using Java NIO WatchService.
public class FileWatcherService implements WatcherService {
// Configuration
private long watchDelayMs = 1000;
private WatchEvent.Kind<?>[] watchEvents = {
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE
};
// Constructors
public FileWatcherService();
public FileWatcherService(ExecutorService executorService);
// WatcherService implementation
@Override
public void start(String name);
@Override
public void watch(Path file, Runnable onChange);
@Override
public void watch(Resource resource, Runnable onChange);
// Configuration methods
public void setWatchDelay(long delay, TimeUnit timeUnit);
public void setWatchEvents(WatchEvent.Kind<?>... eventTypes);
// Advanced watch operations
public void watchDirectory(Path directory,
Predicate<Path> fileFilter,
Consumer<WatchEvent<?>> eventConsumer);
public void watchRecursively(Path rootDirectory,
Consumer<WatchEvent<?>> eventConsumer);
}Path-based watcher service with enhanced filtering and event handling.
public class PathWatcherService implements WatcherService {
// Enhanced filtering
private final Set<PathMatcher> includePatterns;
private final Set<PathMatcher> excludePatterns;
// Constructors
public PathWatcherService();
public PathWatcherService(FileSystem fileSystem);
// Pattern-based filtering
public void addIncludePattern(String pattern);
public void addExcludePattern(String pattern);
public void setIncludePatterns(Collection<String> patterns);
public void setExcludePatterns(Collection<String> patterns);
// Advanced watch operations
public void watchWithFilter(Path path,
PathMatcher filter,
Consumer<WatchEvent<?>> eventConsumer);
}Configuration file watching:
@Component
public class ConfigurationWatcher {
private final FileWatcherService fileWatcher;
private final ConfigurationService configService;
@PostConstruct
public void initializeWatching() {
// Watch main configuration file
Path configFile = Paths.get("application.properties");
fileWatcher.watch(configFile, this::reloadConfiguration);
// Watch configuration directory
Path configDir = Paths.get("config");
fileWatcher.watchDirectory(
configDir,
path -> path.toString().endsWith(".properties"),
this::handleConfigurationChange
);
// Start watching
fileWatcher.start("configuration-watcher");
}
private void reloadConfiguration() {
try {
log.info("Configuration file changed, reloading...");
configService.reload();
log.info("Configuration reloaded successfully");
} catch (Exception e) {
log.error("Failed to reload configuration", e);
}
}
private void handleConfigurationChange(WatchEvent<?> event) {
WatchEvent.Kind<?> kind = event.kind();
Path path = (Path) event.context();
log.info("Configuration change detected: {} - {}", kind.name(), path);
if (kind == StandardWatchEventKinds.ENTRY_MODIFY ||
kind == StandardWatchEventKinds.ENTRY_CREATE) {
reloadConfiguration();
}
}
}Service registry file watching:
@Component
public class ServiceRegistryWatcher {
private final PathWatcherService pathWatcher;
private final ServiceRegistry serviceRegistry;
@PostConstruct
public void startWatching() {
// Configure patterns for service files
pathWatcher.addIncludePattern("*.json");
pathWatcher.addIncludePattern("*.yml");
pathWatcher.addExcludePattern(".*"); // Exclude hidden files
// Watch services directory recursively
Path servicesDir = Paths.get("services");
pathWatcher.watchRecursively(servicesDir, this::handleServiceChange);
pathWatcher.start("service-registry-watcher");
}
private void handleServiceChange(WatchEvent<?> event) {
WatchEvent.Kind<?> kind = event.kind();
Path path = (Path) event.context();
switch (kind.name()) {
case "ENTRY_CREATE", "ENTRY_MODIFY" -> reloadService(path);
case "ENTRY_DELETE" -> removeService(path);
}
}
private void reloadService(Path servicePath) {
try {
RegisteredService service = loadServiceFromFile(servicePath);
serviceRegistry.save(service);
log.info("Service reloaded: {}", service.getName());
} catch (Exception e) {
log.error("Failed to reload service from: {}", servicePath, e);
}
}
}Composite X.509 key manager supporting multiple key sources and selection strategies.
public class CompositeX509KeyManager implements X509KeyManager {
private final List<X509KeyManager> keyManagers;
private final KeySelectionStrategy selectionStrategy;
// Constructors
public CompositeX509KeyManager(List<X509KeyManager> keyManagers);
public CompositeX509KeyManager(List<X509KeyManager> keyManagers,
KeySelectionStrategy strategy);
// X509KeyManager implementation
@Override
public String[] getClientAliases(String keyType, Principal[] issuers);
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket);
@Override
public String[] getServerAliases(String keyType, Principal[] issuers);
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket);
@Override
public X509Certificate[] getCertificateChain(String alias);
@Override
public PrivateKey getPrivateKey(String alias);
// Management methods
public void addKeyManager(X509KeyManager keyManager);
public void removeKeyManager(X509KeyManager keyManager);
public List<X509KeyManager> getKeyManagers();
}Composite X.509 trust manager supporting multiple trust stores and validation strategies.
public class CompositeX509TrustManager implements X509TrustManager {
private final List<X509TrustManager> trustManagers;
private final TrustValidationStrategy validationStrategy;
// Constructors
public CompositeX509TrustManager(List<X509TrustManager> trustManagers);
public CompositeX509TrustManager(List<X509TrustManager> trustManagers,
TrustValidationStrategy strategy);
// X509TrustManager implementation
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException;
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException;
@Override
public X509Certificate[] getAcceptedIssuers();
// Management methods
public void addTrustManager(X509TrustManager trustManager);
public void removeTrustManager(X509TrustManager trustManager);
public List<X509TrustManager> getTrustManagers();
}SSL utility methods for context creation and certificate operations.
@UtilityClass
public class SSLUtils {
// SSL context creation
public static SSLContext createSSLContext(KeyManager[] keyManagers,
TrustManager[] trustManagers);
public static SSLContext createSSLContext(KeyStore keyStore,
char[] keyStorePassword,
KeyStore trustStore);
// Certificate utilities
public static KeyStore createKeyStore(String type);
public static KeyStore loadKeyStore(InputStream inputStream,
char[] password,
String type);
// Trust manager utilities
public static X509TrustManager createTrustAllManager();
public static X509TrustManager createDefaultTrustManager();
// Hostname verification
public static HostnameVerifier createPermissiveHostnameVerifier();
public static HostnameVerifier createStrictHostnameVerifier();
// SSL socket configuration
public static void configureSSLSocket(SSLSocket socket, String[] protocols, String[] cipherSuites);
}SSL configuration for CAS:
@Configuration
@EnableConfigurationProperties(SslProperties.class)
public class SslConfiguration {
@Bean
public SSLContext casSSLContext(SslProperties sslProperties) throws Exception {
// Load multiple key stores
List<X509KeyManager> keyManagers = new ArrayList<>();
// Primary certificate
KeyStore primaryKeyStore = SSLUtils.loadKeyStore(
new FileInputStream(sslProperties.getPrimaryKeyStore()),
sslProperties.getPrimaryKeyStorePassword().toCharArray(),
"PKCS12"
);
KeyManagerFactory primaryKmf = KeyManagerFactory.getInstance("SunX509");
primaryKmf.init(primaryKeyStore, sslProperties.getPrimaryKeyPassword().toCharArray());
keyManagers.addAll(Arrays.asList((X509KeyManager[]) primaryKmf.getKeyManagers()));
// Fallback certificate
if (sslProperties.getFallbackKeyStore() != null) {
KeyStore fallbackKeyStore = SSLUtils.loadKeyStore(
new FileInputStream(sslProperties.getFallbackKeyStore()),
sslProperties.getFallbackKeyStorePassword().toCharArray(),
"PKCS12"
);
KeyManagerFactory fallbackKmf = KeyManagerFactory.getInstance("SunX509");
fallbackKmf.init(fallbackKeyStore, sslProperties.getFallbackKeyPassword().toCharArray());
keyManagers.addAll(Arrays.asList((X509KeyManager[]) fallbackKmf.getKeyManagers()));
}
// Create composite key manager
CompositeX509KeyManager compositeKeyManager = new CompositeX509KeyManager(keyManagers);
// Load trust stores
List<X509TrustManager> trustManagers = new ArrayList<>();
// System trust store
trustManagers.add(SSLUtils.createDefaultTrustManager());
// Custom trust store
if (sslProperties.getTrustStore() != null) {
KeyStore trustStore = SSLUtils.loadKeyStore(
new FileInputStream(sslProperties.getTrustStore()),
sslProperties.getTrustStorePassword().toCharArray(),
"PKCS12"
);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
trustManagers.addAll(Arrays.asList((X509TrustManager[]) tmf.getTrustManagers()));
}
// Create composite trust manager
CompositeX509TrustManager compositeTrustManager = new CompositeX509TrustManager(trustManagers);
// Create SSL context
return SSLUtils.createSSLContext(
new KeyManager[]{compositeKeyManager},
new TrustManager[]{compositeTrustManager}
);
}
}JPA attribute converter for Map to JSON conversion.
@Converter
public class MapToJsonAttributeConverter implements AttributeConverter<Map<String, Object>, String> {
private final ObjectMapper objectMapper;
public MapToJsonAttributeConverter();
public MapToJsonAttributeConverter(ObjectMapper objectMapper);
@Override
public String convertToDatabaseColumn(Map<String, Object> attribute);
@Override
public Map<String, Object> convertToEntityAttribute(String dbData);
}JPA attribute converter for MultiValuedMap to JSON conversion.
@Converter
public class MultivaluedMapToJsonAttributeConverter
implements AttributeConverter<Map<String, List<Object>>, String> {
@Override
public String convertToDatabaseColumn(Map<String, List<Object>> attribute);
@Override
public Map<String, List<Object>> convertToEntityAttribute(String dbData);
}JPA entity with JSON attributes:
@Entity
@Table(name = "user_profiles")
public class UserProfile {
@Id
private String userId;
@Convert(converter = MapToJsonAttributeConverter.class)
@Column(columnDefinition = "TEXT")
private Map<String, Object> attributes;
@Convert(converter = MultivaluedMapToJsonAttributeConverter.class)
@Column(columnDefinition = "TEXT")
private Map<String, List<Object>> preferences;
// getters/setters
}
@Service
public class UserProfileService {
public void updateUserAttributes(String userId, Map<String, Object> newAttributes) {
UserProfile profile = userProfileRepository.findById(userId)
.orElse(new UserProfile(userId));
// Merge attributes
Map<String, Object> existingAttributes = profile.getAttributes();
if (existingAttributes == null) {
existingAttributes = new HashMap<>();
}
existingAttributes.putAll(newAttributes);
profile.setAttributes(existingAttributes);
userProfileRepository.save(profile);
}
}CAS-specific reentrant lock implementation with enhanced monitoring and debugging.
public class CasReentrantLock extends ReentrantLock {
private final String lockName;
private final AtomicLong lockCount = new AtomicLong(0);
private final AtomicLong contentionCount = new AtomicLong(0);
// Constructors
public CasReentrantLock(String lockName);
public CasReentrantLock(String lockName, boolean fair);
// Enhanced locking with monitoring
@Override
public void lock();
@Override
public boolean tryLock();
@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
// Monitoring methods
public String getLockName();
public long getLockCount();
public long getContentionCount();
public LockStatistics getStatistics();
// Debugging utilities
public String getCurrentThreadInfo();
public List<String> getQueuedThreadsInfo();
}Thread-safe service with monitoring:
@Service
public class ThreadSafeTicketService {
private final CasReentrantLock ticketLock = new CasReentrantLock("ticket-generation");
private final AtomicLong ticketCounter = new AtomicLong(0);
public String generateTicket(String serviceId) {
ticketLock.lock();
try {
long ticketId = ticketCounter.incrementAndGet();
String ticket = String.format("ST-%d-%s-%d",
ticketId,
serviceId,
System.currentTimeMillis()
);
log.debug("Generated ticket: {} (lock stats: {})",
ticket, ticketLock.getStatistics());
return ticket;
} finally {
ticketLock.unlock();
}
}
@EventListener
public void onApplicationShutdown(ApplicationEvent event) {
LockStatistics stats = ticketLock.getStatistics();
log.info("Ticket generation lock statistics: {}", stats);
}
}Delegation for virtual thread operations (Java 21+).
public class VirtualThreadDelegate {
// Virtual thread factory methods
public ThreadFactory virtualThreadFactory();
public ThreadFactory virtualThreadFactory(String threadNamePrefix);
// Virtual thread creation
public Thread newVirtualThread(String name, Runnable task);
public Thread newVirtualThread(Runnable task);
// Executor service with virtual threads
public ExecutorService newVirtualThreadExecutor();
public ExecutorService newVirtualThreadExecutor(String threadNamePrefix);
// Utility methods
public boolean isVirtualThreadsSupported();
public CompletableFuture<Void> runAsync(Runnable task);
public <T> CompletableFuture<T> supplyAsync(Supplier<T> supplier);
}Virtual thread integration:
@Configuration
@ConditionalOnJavaVersion(JavaVersion.TWENTY_ONE)
public class VirtualThreadConfiguration {
@Bean
public VirtualThreadDelegate virtualThreadDelegate() {
return new VirtualThreadDelegate();
}
@Bean
@ConditionalOnProperty(name = "cas.virtual-threads.enabled", havingValue = "true")
public ExecutorService virtualThreadExecutor(VirtualThreadDelegate delegate) {
return delegate.newVirtualThreadExecutor("cas-virtual-");
}
}
@Service
public class AsyncProcessingService {
private final VirtualThreadDelegate virtualThreads;
private final ExecutorService virtualExecutor;
public CompletableFuture<ProcessingResult> processAsync(ProcessingRequest request) {
if (virtualThreads.isVirtualThreadsSupported()) {
return virtualThreads.supplyAsync(() -> processRequest(request));
} else {
return CompletableFuture.supplyAsync(() -> processRequest(request), virtualExecutor);
}
}
public void processMultipleAsync(List<ProcessingRequest> requests) {
List<CompletableFuture<Void>> futures = requests.stream()
.map(request -> virtualThreads.runAsync(() -> processRequest(request)))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> log.info("All requests processed"));
}
}Default log message summarizer for reducing verbose logging.
public class DefaultLogMessageSummarizer implements LogMessageSummarizer {
private final Map<String, AtomicInteger> messageCounts;
private final long summaryIntervalMs;
public DefaultLogMessageSummarizer();
public DefaultLogMessageSummarizer(long summaryIntervalMs);
@Override
public String summarize(String message);
@Override
public void reset();
public Map<String, Integer> getMessageSummary();
}Disabled implementation that passes through all messages unchanged.
public class DisabledLogMessageSummarizer implements LogMessageSummarizer {
@Override
public String summarize(String message);
@Override
public void reset();
}Interface for application entry point initialization.
public interface ApplicationEntrypointInitializer {
// Initialization methods
void initialize();
void initialize(String[] args);
void initialize(ApplicationContext context);
// Lifecycle methods
default void beforeInitialization() {}
default void afterInitialization() {}
// Configuration
int getOrder();
boolean isEnabled();
}General application utilities for runtime information and configuration.
@UtilityClass
public class ApplicationUtils {
// Application information
public static String getApplicationVersion();
public static String getApplicationName();
public static Properties getApplicationProperties();
// Runtime information
public static RuntimeInformation getRuntimeInformation();
public static MemoryInformation getMemoryInformation();
public static SystemInformation getSystemInformation();
// Configuration utilities
public static boolean isProfileActive(String profile);
public static String[] getActiveProfiles();
public static Properties getEnvironmentProperties();
}This comprehensive set of specialized utilities provides essential infrastructure components for building robust, scalable, and maintainable CAS applications with proper monitoring, security, and performance characteristics.
Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-util-api