0
# Functional Programming
1
2
Advanced functional programming utilities with enhanced exception handling, conditional operations, retry mechanisms, and composable functions for robust and elegant code patterns.
3
4
## Core Imports
5
6
```java
7
import org.apereo.cas.util.function.FunctionUtils;
8
import org.jooq.lambda.fi.util.function.CheckedFunction;
9
import org.jooq.lambda.fi.util.function.CheckedConsumer;
10
import org.jooq.lambda.fi.util.function.CheckedSupplier;
11
import org.springframework.retry.RetryCallback;
12
import java.util.function.*;
13
import java.util.List;
14
```
15
16
## FunctionUtils
17
18
Comprehensive utility class providing functional programming constructs with exception handling and conditional execution patterns.
19
20
```java { .api }
21
@UtilityClass
22
public class FunctionUtils {
23
24
// Conditional Functions - Return modified values based on conditions
25
public static <T, R> Function<T, R> doIf(Predicate<Object> condition,
26
Supplier<R> trueFunction,
27
Supplier<R> falseFunction);
28
29
public static <T, R> Function<T, R> doIf(Predicate<T> condition,
30
CheckedFunction<T, R> trueFunction,
31
CheckedFunction<T, R> falseFunction);
32
33
// Conditional Consumers - Execute actions based on conditions
34
public static <T> Consumer<T> doIf(boolean condition,
35
Consumer<T> trueFunction,
36
Consumer<T> falseFunction);
37
38
public static <T> Consumer<T> doIf(boolean condition,
39
Consumer<T> trueFunction);
40
41
// Conditional Suppliers - Supply values based on conditions
42
public static <R> Supplier<R> doIf(boolean condition,
43
Supplier<R> trueFunction,
44
Supplier<R> falseFunction);
45
46
// String-specific conditional operations
47
public static <T> void doIfBlank(CharSequence input, CheckedConsumer<T> trueFunction);
48
49
public static <T> T doIfNotBlank(CharSequence input,
50
CheckedSupplier<T> trueFunction,
51
CheckedSupplier<T> falseFunction);
52
53
public static <T extends CharSequence> void doIfNotBlank(T input, CheckedConsumer<T> trueFunction);
54
55
// Null-safe operations
56
public static <R> R doIfNotNull(Object input, CheckedSupplier<R> trueFunction);
57
58
public static <R> Supplier<R> doIfNotNull(Object input,
59
CheckedSupplier<R> trueFunction,
60
CheckedSupplier<R> falseFunction);
61
62
public static <T> void doIfNotNull(T input, CheckedConsumer<T> trueFunction);
63
64
public static <T> void doIfNotNull(T input,
65
CheckedConsumer<T> trueFunction,
66
CheckedConsumer<T> falseFunction);
67
68
// Null condition operations
69
public static <T> void doIfNull(T input, CheckedConsumer<T> trueFunction);
70
71
public static <T> void doIfNull(T input,
72
CheckedConsumer<T> trueFunction,
73
CheckedConsumer<T> falseFunction);
74
75
public static <R> Supplier<R> doIfNull(Object input,
76
Supplier<R> trueFunction,
77
Supplier<R> falseFunction);
78
79
public static <T> void doIfNotNull(T obj, Consumer<T> consumer);
80
81
public static <T> void doIfNull(T obj, Runnable action);
82
83
// Exception handling with functions
84
public static <T, R> Function<T, R> doAndHandle(CheckedFunction<T, R> function,
85
CheckedFunction<Throwable, R> errorHandler);
86
87
public static <R> Consumer<R> doAndHandle(CheckedConsumer<R> function,
88
CheckedFunction<Throwable, R> errorHandler);
89
90
public static <R> R doAndHandle(CheckedSupplier<R> function);
91
92
public static <R> void doAndHandle(CheckedConsumer<R> function);
93
94
public static <R> Supplier<R> doAndHandle(CheckedSupplier<R> function,
95
CheckedFunction<Throwable, R> errorHandler);
96
97
// Conditional execution with predicates
98
public static <T> void doWhen(T obj, Predicate<T> condition, Consumer<T> consumer);
99
100
// Exception-safe operations
101
public static boolean doWithoutThrows(Runnable action);
102
103
// Unchecked exception handling
104
public static <T> T doUnchecked(Supplier<T> supplier);
105
106
public static void doUnchecked(Runnable action);
107
108
// Retry operations
109
public static <T> T doAndRetry(RetryCallback<T, Exception> callback) throws Exception;
110
111
public static <T> T doAndRetry(RetryCallback<T, Exception> callback, int maximumAttempts) throws Exception;
112
113
public static <T> T doAndRetry(List<Class<? extends Throwable>> clazzes,
114
RetryCallback<T, Exception> callback) throws Exception;
115
116
public static <T> T doAndRetry(List<Class<? extends Throwable>> clazzes,
117
RetryCallback<T, Exception> callback,
118
int maximumAttempts) throws Exception;
119
120
// Validation helpers
121
public static String throwIfBlank(String str, String message);
122
123
public static <T> T throwIfNull(T obj, String message);
124
125
public static void throwIf(BooleanSupplier condition, String message);
126
127
// Return value helpers
128
public static <T> T doAndReturn(Runnable action, T returnValue);
129
130
public static <T> T doAndThrow(Runnable action, RuntimeException exception);
131
132
public static <T> T doAndThrowUnchecked(Runnable action, Exception exception);
133
}
134
```
135
136
### Usage Examples
137
138
**Conditional operations with enhanced readability:**
139
```java
140
@Service
141
public class UserServiceImpl implements UserService {
142
143
private final UserRepository userRepository;
144
private final EmailService emailService;
145
146
public void processUserRegistration(UserRegistrationRequest request) {
147
// Conditional validation with clear semantics
148
String email = FunctionUtils.throwIfBlank(request.getEmail(), "Email is required");
149
String username = FunctionUtils.throwIfBlank(request.getUsername(), "Username is required");
150
151
// Conditional processing based on user type
152
Function<UserRegistrationRequest, User> userCreator = FunctionUtils.doIf(
153
req -> req.getAccountType() == AccountType.PREMIUM,
154
req -> createPremiumUser(req),
155
req -> createStandardUser(req)
156
);
157
158
User user = userCreator.apply(request);
159
userRepository.save(user);
160
161
// Conditional email sending
162
Consumer<User> emailSender = FunctionUtils.doIf(
163
u -> u.isEmailNotificationEnabled(),
164
u -> emailService.sendWelcomeEmail(u.getEmail())
165
);
166
167
emailSender.accept(user);
168
}
169
170
public Optional<UserProfile> getUserProfile(String userId) {
171
return FunctionUtils.doIfNotBlank(userId, id -> {
172
User user = userRepository.findById(id);
173
return FunctionUtils.doIfNotNull(user, this::createUserProfile);
174
});
175
}
176
}
177
```
178
179
**Exception handling with graceful degradation:**
180
```java
181
@Service
182
public class ExternalApiService {
183
184
private final HttpClient httpClient;
185
private final CacheManager cacheManager;
186
187
public ApiResponse fetchUserData(String userId) {
188
// Primary operation with exception handling and fallback
189
Function<String, ApiResponse> fetchFromApi = FunctionUtils.doAndHandle(
190
id -> callExternalApi(id),
191
IOException.class,
192
ex -> {
193
log.warn("API call failed, trying cache", ex);
194
return getCachedResponse(id);
195
}
196
);
197
198
// Retry mechanism for critical operations
199
return FunctionUtils.doAndRetry(
200
() -> fetchFromApi.apply(userId),
201
3, // Max retries
202
1000, // Delay between retries
203
IOException.class, // Retryable exceptions
204
TimeoutException.class
205
);
206
}
207
208
public UserPreferences getUserPreferences(String userId) {
209
// Multiple fallback strategies
210
Supplier<UserPreferences> preferenceSupplier = FunctionUtils.doIf(
211
() -> isUserCacheEnabled(),
212
() -> getCachedPreferences(userId),
213
() -> getDefaultPreferences()
214
);
215
216
return FunctionUtils.doAndHandle(
217
preferenceSupplier,
218
Exception.class,
219
ex -> {
220
log.error("Failed to load user preferences", ex);
221
return UserPreferences.getDefaults();
222
}
223
);
224
}
225
226
public void updateUserSettings(String userId, Map<String, Object> settings) {
227
// Conditional processing with validation
228
FunctionUtils.doWhen(settings,
229
s -> !s.isEmpty(),
230
s -> {
231
validateSettings(s);
232
persistSettings(userId, s);
233
clearUserCache(userId);
234
}
235
);
236
237
// Null-safe notification
238
FunctionUtils.doIfNotNull(settings.get("email"),
239
email -> notificationService.updateEmailPreference(userId, (String) email)
240
);
241
}
242
}
243
```
244
245
**Advanced retry patterns with exponential backoff:**
246
```java
247
@Component
248
public class ResilientDataService {
249
250
public <T> T executeWithRetry(Supplier<T> operation, String operationName) {
251
return FunctionUtils.doAndRetry(
252
() -> {
253
log.debug("Executing operation: {}", operationName);
254
T result = operation.get();
255
log.debug("Operation completed successfully: {}", operationName);
256
return result;
257
},
258
5, // Max retries
259
1000, // Base delay
260
SQLException.class, // Database exceptions
261
ConnectException.class, // Network exceptions
262
SocketTimeoutException.class // Timeout exceptions
263
);
264
}
265
266
public CompletableFuture<String> processDataAsync(String dataId) {
267
return CompletableFuture.supplyAsync(() ->
268
FunctionUtils.doAndHandle(
269
() -> processData(dataId),
270
Exception.class,
271
ex -> {
272
log.error("Async data processing failed for: {}", dataId, ex);
273
return "ERROR: " + ex.getMessage();
274
}
275
)
276
);
277
}
278
279
public void batchProcess(List<String> dataIds) {
280
dataIds.parallelStream()
281
.forEach(id ->
282
FunctionUtils.doWhen(id,
283
Objects::nonNull,
284
dataId -> FunctionUtils.doWithoutThrows(() -> processData(dataId))
285
)
286
);
287
}
288
}
289
```
290
291
**Validation and transformation pipelines:**
292
```java
293
@Service
294
public class DataValidationService {
295
296
public ProcessedData validateAndTransform(RawData rawData) {
297
// Validation pipeline with early returns
298
RawData validated = FunctionUtils.doAndReturn(
299
() -> FunctionUtils.throwIfNull(rawData, "Raw data cannot be null"),
300
rawData
301
);
302
303
// Transformation pipeline with conditional steps
304
Function<RawData, ProcessedData> transformationPipeline = data -> {
305
// Step 1: Basic sanitization
306
data = FunctionUtils.doIfNotNull(data, this::sanitizeData);
307
308
// Step 2: Conditional enrichment
309
Function<RawData, RawData> enricher = FunctionUtils.doIf(
310
d -> d.requiresEnrichment(),
311
d -> enrichData(d),
312
d -> d // No enrichment needed
313
);
314
data = enricher.apply(data);
315
316
// Step 3: Format conversion
317
return convertToProcessedData(data);
318
};
319
320
// Execute with error handling
321
return FunctionUtils.doAndHandle(
322
() -> transformationPipeline.apply(validated),
323
ValidationException.class,
324
ex -> {
325
log.error("Validation failed", ex);
326
throw new ProcessingException("Data validation failed", ex);
327
}
328
);
329
}
330
331
public ValidationResult validateBusinessRules(ProcessedData data) {
332
List<ValidationError> errors = new ArrayList<>();
333
334
// Conditional validation rules
335
Consumer<ProcessedData> validator = FunctionUtils.doIf(
336
d -> d.getType() == DataType.FINANCIAL,
337
d -> validateFinancialRules(d, errors)
338
).andThen(FunctionUtils.doIf(
339
d -> d.hasPersonalInfo(),
340
d -> validatePrivacyRules(d, errors)
341
)).andThen(FunctionUtils.doIf(
342
d -> d.isExternalSource(),
343
d -> validateExternalSourceRules(d, errors)
344
));
345
346
validator.accept(data);
347
348
return errors.isEmpty() ? ValidationResult.success() : ValidationResult.failure(errors);
349
}
350
}
351
```
352
353
## ComposableFunction Interface
354
355
Interface extending Function for composable operations with enhanced chaining capabilities.
356
357
```java { .api }
358
@FunctionalInterface
359
public interface ComposableFunction<T, R> extends Function<T, R> {
360
361
// Enhanced composition methods
362
default <V> ComposableFunction<T, V> andThenComposable(Function<? super R, ? extends V> after);
363
364
default <V> ComposableFunction<V, R> composeComposable(Function<? super V, ? extends T> before);
365
366
// Conditional composition
367
default ComposableFunction<T, R> composeIf(Predicate<T> condition,
368
Function<T, T> preprocessor);
369
370
// Exception-safe composition
371
default ComposableFunction<T, Optional<R>> safely();
372
373
// Retry composition
374
default ComposableFunction<T, R> withRetry(int maxRetries, long delayMillis);
375
376
// Factory methods
377
static <T, R> ComposableFunction<T, R> of(Function<T, R> function);
378
379
static <T> ComposableFunction<T, T> identity();
380
}
381
```
382
383
### Usage Examples
384
385
**Composable function pipelines:**
386
```java
387
@Service
388
public class DataProcessingPipeline {
389
390
public ProcessingResult processUserData(UserInput input) {
391
// Create composable processing pipeline
392
ComposableFunction<UserInput, ProcessingResult> pipeline = ComposableFunction
393
.<UserInput>identity()
394
.andThenComposable(this::validateInput)
395
.andThenComposable(this::sanitizeInput)
396
.andThenComposable(this::enrichInput)
397
.andThenComposable(this::transformInput)
398
.andThenComposable(this::persistInput)
399
.andThenComposable(this::createResult);
400
401
// Execute pipeline with error handling
402
return pipeline.safely()
403
.withRetry(3, 1000)
404
.apply(input)
405
.orElse(ProcessingResult.failed("Processing pipeline failed"));
406
}
407
408
public ComposableFunction<String, User> createUserPipeline() {
409
return ComposableFunction
410
.<String>of(this::validateUserId)
411
.composeIf(Objects::nonNull, this::normalizeUserId)
412
.andThenComposable(this::fetchUserFromRepository)
413
.andThenComposable(this::enrichUserData)
414
.safely(); // Make the entire pipeline safe
415
}
416
}
417
```
418
419
**Advanced composition with conditions:**
420
```java
421
@Component
422
public class ConditionalProcessingService {
423
424
public String processMessage(Message message, ProcessingContext context) {
425
// Build conditional processing pipeline
426
ComposableFunction<Message, String> processor = ComposableFunction
427
.<Message>identity()
428
.composeIf(msg -> msg.isEncrypted(), this::decryptMessage)
429
.composeIf(msg -> msg.needsSanitization(), this::sanitizeMessage)
430
.andThenComposable(msg -> applyBusinessRules(msg, context))
431
.composeIf(msg -> context.isAuditEnabled(), this::auditMessage)
432
.andThenComposable(this::formatOutput);
433
434
return processor.apply(message);
435
}
436
437
public ComposableFunction<Request, Response> createAuthenticationPipeline() {
438
return ComposableFunction
439
.<Request>of(this::extractCredentials)
440
.andThenComposable(this::validateCredentials)
441
.composeIf(creds -> creds.isMfaRequired(), this::validateMfaToken)
442
.andThenComposable(this::createAuthenticationResult)
443
.andThenComposable(this::generateTokens)
444
.andThenComposable(this::createResponse)
445
.withRetry(2, 500); // Retry on transient failures
446
}
447
}
448
```
449
450
## Complete Integration Examples
451
452
### Functional Configuration Service
453
454
```java
455
@Service
456
@Slf4j
457
public class FunctionalConfigurationService {
458
459
private final Map<String, Supplier<Object>> configurationSuppliers;
460
461
public FunctionalConfigurationService() {
462
this.configurationSuppliers = new ConcurrentHashMap<>();
463
initializeDefaultSuppliers();
464
}
465
466
private void initializeDefaultSuppliers() {
467
// Database configuration with fallback
468
registerConfigSupplier("database.url",
469
FunctionUtils.doIf(
470
() -> isDatabaseAvailable(),
471
() -> getDatabaseUrl(),
472
() -> getDefaultDatabaseUrl()
473
)
474
);
475
476
// Cache configuration with validation
477
registerConfigSupplier("cache.enabled",
478
FunctionUtils.doAndHandle(
479
() -> Boolean.parseBoolean(getProperty("cache.enabled", "true")),
480
Exception.class,
481
ex -> {
482
log.warn("Failed to parse cache.enabled, using default", ex);
483
return true;
484
}
485
)
486
);
487
488
// External service URLs with retry
489
registerConfigSupplier("external.api.url",
490
() -> FunctionUtils.doAndRetry(
491
() -> validateAndGetExternalUrl(),
492
3,
493
2000,
494
ConnectException.class
495
)
496
);
497
}
498
499
public <T> T getConfiguration(String key, Class<T> type) {
500
Supplier<Object> supplier = configurationSuppliers.get(key);
501
502
return FunctionUtils.doIfNotNull(supplier,
503
s -> FunctionUtils.doAndHandle(
504
() -> type.cast(s.get()),
505
ClassCastException.class,
506
ex -> {
507
log.error("Configuration cast failed for key: {}", key, ex);
508
return null;
509
}
510
)
511
);
512
}
513
514
public void registerConfigSupplier(String key, Supplier<Object> supplier) {
515
configurationSuppliers.put(key, supplier);
516
}
517
518
// Functional configuration validation
519
public ValidationResult validateAllConfigurations() {
520
List<String> errors = configurationSuppliers.entrySet()
521
.parallelStream()
522
.map(entry -> validateConfiguration(entry.getKey(), entry.getValue()))
523
.filter(Optional::isPresent)
524
.map(Optional::get)
525
.collect(Collectors.toList());
526
527
return errors.isEmpty() ?
528
ValidationResult.success() :
529
ValidationResult.failure(errors);
530
}
531
532
private Optional<String> validateConfiguration(String key, Supplier<Object> supplier) {
533
return FunctionUtils.doAndHandle(
534
() -> {
535
Object value = supplier.get();
536
FunctionUtils.throwIfNull(value, "Configuration value is null");
537
return Optional.<String>empty();
538
},
539
Exception.class,
540
ex -> Optional.of("Configuration '" + key + "' failed: " + ex.getMessage())
541
);
542
}
543
}
544
```
545
546
### Functional Event Processing System
547
548
```java
549
@Component
550
public class FunctionalEventProcessor {
551
552
private final Map<EventType, ComposableFunction<Event, ProcessingResult>> processors;
553
554
public FunctionalEventProcessor() {
555
this.processors = new HashMap<>();
556
initializeProcessors();
557
}
558
559
private void initializeProcessors() {
560
// User event processor
561
processors.put(EventType.USER_ACTION,
562
ComposableFunction
563
.<Event>of(this::validateUserEvent)
564
.composeIf(e -> e.requiresAuthentication(), this::authenticateEvent)
565
.andThenComposable(this::enrichUserEvent)
566
.composeIf(e -> e.isAuditable(), this::auditEvent)
567
.andThenComposable(this::processUserAction)
568
.safely()
569
.withRetry(2, 1000)
570
);
571
572
// System event processor
573
processors.put(EventType.SYSTEM_EVENT,
574
ComposableFunction
575
.<Event>of(this::validateSystemEvent)
576
.andThenComposable(this::enrichSystemEvent)
577
.andThenComposable(this::processSystemEvent)
578
.safely()
579
);
580
581
// Security event processor
582
processors.put(EventType.SECURITY_EVENT,
583
ComposableFunction
584
.<Event>of(this::validateSecurityEvent)
585
.andThenComposable(this::enrichSecurityEvent)
586
.composeIf(e -> e.isCritical(), this::alertSecurityTeam)
587
.andThenComposable(this::processSecurityEvent)
588
.safely()
589
.withRetry(5, 500) // More aggressive retry for security events
590
);
591
}
592
593
public CompletableFuture<ProcessingResult> processEventAsync(Event event) {
594
return CompletableFuture.supplyAsync(() ->
595
FunctionUtils.doIfNotNull(event,
596
e -> {
597
ComposableFunction<Event, ProcessingResult> processor =
598
processors.get(e.getType());
599
600
return FunctionUtils.doIfNotNull(processor,
601
p -> p.apply(e)
602
);
603
}
604
)
605
);
606
}
607
608
public void processEventBatch(List<Event> events) {
609
events.parallelStream()
610
.forEach(event ->
611
FunctionUtils.doWhen(event,
612
Objects::nonNull,
613
e -> FunctionUtils.doWithoutThrows(() ->
614
processEventAsync(e).get(5, TimeUnit.SECONDS)
615
)
616
)
617
);
618
}
619
}
620
```
621
622
This functional programming library provides powerful abstractions for writing clean, composable, and resilient code with comprehensive error handling and conditional logic support, essential for building robust CAS applications.