CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-github-ben-manes-caffeine--jcache

JSR-107 JCache compatibility adapter for Caffeine caching library

Pending
Overview
Eval results
Files

integration.mddocs/

Integration Features

The Integration Features system provides support for cache loading, writing, copying, and entry processing to integrate with external data sources and custom business logic. These features enable seamless integration between the cache and external systems like databases, web services, and custom data processing pipelines.

Capabilities

Cache Loading

Integration with external data sources through cache loaders for read-through functionality.

/**
 * Adapts JCache CacheLoader to Caffeine CacheLoader for integration
 */
public final class JCacheLoaderAdapter<K, V> implements CacheLoader<K, Expirable<V>> {
    
    /**
     * Load a single entry from external data source
     * @param key the key to load
     * @return wrapped value with expiration information
     */
    public @Nullable Expirable<V> load(K key);
    
    /**
     * Load multiple entries from external data source
     * @param keys the keys to load
     * @return map of loaded key-value pairs
     */
    public Map<K, Expirable<V>> loadAll(Set<? extends K> keys);
}

Usage Examples:

// Implement custom cache loader
public class DatabaseUserLoader implements CacheLoader<String, User> {
    private final UserRepository userRepository;
    
    public DatabaseUserLoader(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public User load(String userId) throws CacheLoaderException {
        try {
            User user = userRepository.findById(userId);
            if (user == null) {
                throw new CacheLoaderException("User not found: " + userId);
            }
            return user;
        } catch (Exception e) {
            throw new CacheLoaderException("Failed to load user: " + userId, e);
        }
    }
    
    @Override
    public Map<String, User> loadAll(Iterable<? extends String> userIds) throws CacheLoaderException {
        try {
            Map<String, User> users = new HashMap<>();
            for (String userId : userIds) {
                User user = userRepository.findById(userId);
                if (user != null) {
                    users.put(userId, user);
                }
            }
            return users;
        } catch (Exception e) {
            throw new CacheLoaderException("Failed to load users", e);
        }
    }
}

// Configure cache with loader
CaffeineConfiguration<String, User> config = new CaffeineConfiguration<String, User>()
    .setTypes(String.class, User.class)
    .setCacheLoaderFactory(() -> new DatabaseUserLoader(userRepository))
    .setReadThrough(true);

Cache<String, User> userCache = cacheManager.createCache("users", config);

// Automatic loading on cache miss
User user = userCache.get("user123"); // Loads from database if not cached

Cache Writing

Integration with external data sources through cache writers for write-through functionality.

/**
 * Disabled cache writer implementation for caches without write-through
 */
public enum DisabledCacheWriter implements CacheWriter<Object, Object> {
    INSTANCE;
    
    /**
     * Get singleton instance of disabled cache writer
     * @return the singleton instance
     */
    public static CacheWriter<Object, Object> get();
    
    /**
     * No-op write operation
     * @param entry the entry to write (ignored)
     */
    public void write(Cache.Entry<? extends Object, ? extends Object> entry);
    
    /**
     * No-op write all operation
     * @param entries the entries to write (ignored)
     */
    public void writeAll(Collection<Cache.Entry<? extends Object, ? extends Object>> entries);
    
    /**
     * No-op delete operation
     * @param key the key to delete (ignored)
     */
    public void delete(Object key);
    
    /**
     * No-op delete all operation
     * @param keys the keys to delete (ignored)
     */
    public void deleteAll(Collection<?> keys);
}

Usage Examples:

// Implement custom cache writer
public class DatabaseUserWriter implements CacheWriter<String, User> {
    private final UserRepository userRepository;
    
    public DatabaseUserWriter(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Override
    public void write(Cache.Entry<? extends String, ? extends User> entry) throws CacheWriterException {
        try {
            userRepository.save(entry.getValue());
        } catch (Exception e) {
            throw new CacheWriterException("Failed to write user: " + entry.getKey(), e);
        }
    }
    
    @Override
    public void writeAll(Collection<Cache.Entry<? extends String, ? extends User>> entries) 
            throws CacheWriterException {
        try {
            List<User> users = entries.stream()
                .map(Cache.Entry::getValue)
                .collect(Collectors.toList());
            userRepository.saveAll(users);
        } catch (Exception e) {
            throw new CacheWriterException("Failed to write users", e);
        }
    }
    
    @Override
    public void delete(Object key) throws CacheWriterException {
        try {
            userRepository.deleteById((String) key);
        } catch (Exception e) {
            throw new CacheWriterException("Failed to delete user: " + key, e);
        }
    }
    
    @Override
    public void deleteAll(Collection<?> keys) throws CacheWriterException {
        try {
            List<String> userIds = keys.stream()
                .map(String.class::cast)
                .collect(Collectors.toList());
            userRepository.deleteAllById(userIds);
        } catch (Exception e) {
            throw new CacheWriterException("Failed to delete users", e);
        }
    }
}

// Configure cache with writer
CaffeineConfiguration<String, User> config = new CaffeineConfiguration<String, User>()
    .setTypes(String.class, User.class)
    .setCacheWriterFactory(() -> new DatabaseUserWriter(userRepository))
    .setWriteThrough(true);

Cache<String, User> userCache = cacheManager.createCache("users", config);

// Automatic writing to database
userCache.put("user123", new User("John Doe")); // Also saves to database
userCache.remove("user123"); // Also deletes from database

Copy Support

Support for store-by-value semantics through configurable copying strategies.

/**
 * Interface for copying objects for store-by-value semantics
 */
public interface Copier {
    
    /**
     * Copy an object using the specified ClassLoader
     * @param object the object to copy
     * @param classLoader the ClassLoader for deserialization
     * @return copied object
     */
    public <T> T copy(T object, ClassLoader classLoader);
    
    /**
     * Get identity copier that returns objects unchanged
     * @return identity copier instance
     */
    public static Copier identity();
}

Java Serialization Copier

Default implementation using Java serialization for copying objects.

/**
 * Copier implementation using Java serialization
 */
public class JavaSerializationCopier extends AbstractCopier<byte[]> {
    
    /**
     * Create new Java serialization copier
     */
    public JavaSerializationCopier();
    
    /**
     * Serialize object to byte array
     * @param object the object to serialize
     * @param classLoader the ClassLoader for serialization
     * @return serialized byte array
     */
    protected byte[] serialize(Object object, ClassLoader classLoader);
    
    /**
     * Deserialize byte array to object
     * @param data the serialized data
     * @param classLoader the ClassLoader for deserialization
     * @return deserialized object
     */
    protected Object deserialize(byte[] data, ClassLoader classLoader);
}

Usage Examples:

// Custom copier implementation using JSON
public class JsonCopier implements Copier {
    private final ObjectMapper objectMapper;
    
    public JsonCopier() {
        this.objectMapper = new ObjectMapper();
    }
    
    @Override
    public <T> T copy(T object, ClassLoader classLoader) {
        if (object == null) {
            return null;
        }
        
        try {
            // Serialize to JSON and deserialize back
            String json = objectMapper.writeValueAsString(object);
            @SuppressWarnings("unchecked")
            Class<T> clazz = (Class<T>) object.getClass();
            return objectMapper.readValue(json, clazz);
        } catch (Exception e) {
            throw new RuntimeException("Failed to copy object", e);
        }
    }
}

// Configure cache with custom copier
CaffeineConfiguration<String, User> config = new CaffeineConfiguration<String, User>()
    .setTypes(String.class, User.class)
    .setStoreByValue(true)
    .setCopierFactory(() -> new JsonCopier());

Cache<String, User> userCache = cacheManager.createCache("users", config);

// Objects are copied on store and retrieve
User original = new User("John Doe");
userCache.put("user1", original);
User retrieved = userCache.get("user1"); // Different instance, same data
assert original != retrieved; // Different object references
assert original.equals(retrieved); // Same data

Entry Processing

Support for atomic entry processing operations with mutable entry interface.

/**
 * Mutable entry implementation for entry processors
 */
public final class EntryProcessorEntry<K, V> implements MutableEntry<K, V> {
    
    /**
     * Get the entry key
     * @return the key of this entry
     */
    public K getKey();
    
    /**
     * Get the entry value
     * @return the value of this entry, or null if not present
     */
    public V getValue();
    
    /**
     * Check if the entry exists in the cache
     * @return true if the entry exists
     */
    public boolean exists();
    
    /**
     * Remove this entry from the cache
     */
    public void remove();
    
    /**
     * Set the value of this entry
     * @param value the new value to set
     */
    public void setValue(V value);
    
    /**
     * Unwrap this entry to a specific type
     * @param clazz the class to unwrap to
     * @return unwrapped instance
     */
    public <T> T unwrap(Class<T> clazz);
}

/**
 * Entry processor action enumeration
 */
public enum Action {
    NONE,     // No action performed
    READ,     // Entry was read
    CREATED,  // Entry was created
    LOADED,   // Entry was loaded from external source
    UPDATED,  // Entry was updated
    DELETED   // Entry was deleted
}

Usage Examples:

// Counter increment entry processor
EntryProcessor<String, AtomicInteger, Integer> incrementProcessor = 
    (entry, arguments) -> {
        AtomicInteger counter = entry.getValue();
        if (counter == null) {
            counter = new AtomicInteger(0);
        }
        int newValue = counter.incrementAndGet();
        entry.setValue(counter);
        return newValue;
    };

// Conditional update entry processor
EntryProcessor<String, User, Boolean> updateEmailProcessor = 
    (entry, arguments) -> {
        User user = entry.getValue();
        if (user == null) {
            return false;
        }
        
        String newEmail = (String) arguments[0];
        if (!isValidEmail(newEmail)) {
            return false;
        }
        
        user.setEmail(newEmail);
        entry.setValue(user);
        return true;
    };

// Complex business logic processor
EntryProcessor<String, Account, TransactionResult> transferProcessor = 
    (entry, arguments) -> {
        Account account = entry.getValue();
        BigDecimal amount = (BigDecimal) arguments[0];
        String transactionId = (String) arguments[1];
        
        if (account == null) {
            return new TransactionResult(false, "Account not found");
        }
        
        if (account.getBalance().compareTo(amount) < 0) {
            return new TransactionResult(false, "Insufficient funds");
        }
        
        // Perform transfer
        account.setBalance(account.getBalance().subtract(amount));
        account.addTransaction(new Transaction(transactionId, amount));
        entry.setValue(account);
        
        return new TransactionResult(true, "Transfer completed");
    };

// Use entry processors
Integer newCount = cache.invoke("counter1", incrementProcessor);
Boolean emailUpdated = cache.invoke("user123", updateEmailProcessor, "new@example.com");
TransactionResult result = cache.invoke("account456", transferProcessor, 
    new BigDecimal("100.00"), "txn-789");

Integration Patterns

Common integration patterns for external systems.

// Database integration pattern
public class DatabaseIntegratedCache<K, V> {
    private final Cache<K, V> cache;
    private final Repository<K, V> repository;
    
    public DatabaseIntegratedCache(Cache<K, V> cache, Repository<K, V> repository) {
        this.cache = cache;
        this.repository = repository;
    }
    
    // Write-behind pattern
    public void putAsync(K key, V value) {
        cache.put(key, value);
        CompletableFuture.runAsync(() -> {
            try {
                repository.save(key, value);
            } catch (Exception e) {
                // Handle async write failure
                handleWriteFailure(key, value, e);
            }
        });
    }
    
    // Cache-aside pattern
    public V getWithFallback(K key) {
        V value = cache.get(key);
        if (value == null) {
            value = repository.findById(key);
            if (value != null) {
                cache.put(key, value);
            }
        }
        return value;
    }
    
    private void handleWriteFailure(K key, V value, Exception e) {
        // Implement failure handling strategy
    }
}

// Web service integration pattern  
public class WebServiceCacheLoader implements CacheLoader<String, ApiResponse> {
    private final WebClient webClient;
    private final CircuitBreaker circuitBreaker;
    
    @Override
    public ApiResponse load(String endpoint) throws CacheLoaderException {
        return circuitBreaker.executeSupplier(() -> {
            try {
                return webClient.get()
                    .uri(endpoint)
                    .retrieve()
                    .bodyToMono(ApiResponse.class)
                    .block(Duration.ofSeconds(10));
            } catch (Exception e) {
                throw new CacheLoaderException("Failed to load from web service", e);
            }
        });
    }
}

// Event-driven invalidation pattern
public class EventDrivenCacheInvalidation {
    private final Cache<String, Object> cache;
    private final EventBus eventBus;
    
    public EventDrivenCacheInvalidation(Cache<String, Object> cache, EventBus eventBus) {
        this.cache = cache;
        this.eventBus = eventBus;
        
        // Register for invalidation events
        eventBus.register(this);
    }
    
    @Subscribe
    public void handleInvalidationEvent(CacheInvalidationEvent event) {
        if (event.isInvalidateAll()) {
            cache.removeAll();
        } else {
            cache.removeAll(event.getKeysToInvalidate());
        }
    }
}

Bulk Operations Integration

Efficient bulk operations for external system integration.

public class BulkOperationHelper {
    
    // Bulk load with batching
    public static <K, V> void bulkLoadWithBatching(
            Cache<K, V> cache, 
            Set<K> keys, 
            CacheLoader<K, V> loader,
            int batchSize) {
        
        List<K> keyList = new ArrayList<>(keys);
        for (int i = 0; i < keyList.size(); i += batchSize) {
            int endIndex = Math.min(i + batchSize, keyList.size());
            List<K> batch = keyList.subList(i, endIndex);
            
            try {
                Map<K, V> loaded = loader.loadAll(batch);
                cache.putAll(loaded);
            } catch (Exception e) {
                // Handle batch failure - could retry individual items
                handleBatchFailure(cache, batch, loader, e);
            }
        }
    }
    
    // Bulk write with error handling
    public static <K, V> void bulkWriteWithErrorHandling(
            Cache<K, V> cache,
            Map<K, V> entries,
            CacheWriter<K, V> writer) {
        
        try {
            List<Cache.Entry<K, V>> entryList = entries.entrySet().stream()
                .map(e -> new SimpleEntry<>(e.getKey(), e.getValue()))
                .collect(Collectors.toList());
            
            writer.writeAll(entryList);
            cache.putAll(entries);
        } catch (CacheWriterException e) {
            // Fall back to individual writes
            for (Map.Entry<K, V> entry : entries.entrySet()) {
                try {
                    writer.write(new SimpleEntry<>(entry.getKey(), entry.getValue()));
                    cache.put(entry.getKey(), entry.getValue());
                } catch (Exception individualError) {
                    // Log individual failure but continue
                    handleIndividualWriteFailure(entry.getKey(), entry.getValue(), individualError);
                }
            }
        }
    }
    
    private static <K, V> void handleBatchFailure(Cache<K, V> cache, List<K> batch, 
                                                 CacheLoader<K, V> loader, Exception e) {
        // Implementation depends on failure handling strategy
    }
    
    private static <K, V> void handleIndividualWriteFailure(K key, V value, Exception e) {
        // Implementation depends on failure handling strategy
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-github-ben-manes-caffeine--jcache

docs

cache-management.md

cache-operations.md

configuration.md

events.md

index.md

integration.md

management.md

spi.md

tile.json