Cache is closed causing an exception while running test suite - ehcache

I'm experiencing a problem similar to the described in this question.
I have a test suite that runs fine in development environment. One of the tests fails when executed in Bitbucket Pipelines with the following exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Cache[model.Role] is closed; nested exception is java.lang.IllegalStateException: Cache[model.Role] is closed
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:364)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
....
I want to try the accepted solution but I don't know how to apply it to my project. The second solution depends on ehcache.xml file. I don't have this file, everything is configured in JavaConfig. How can I adopt the proposed solutions for EhCache + JCache (JSR-107) in JavaConfig?
My cache configuration:
#Configuration
#EnableCaching
public class CacheConfig {
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration =
Eh107Configuration.fromEhcacheCacheConfiguration(CacheConfigurationBuilder
.newCacheConfigurationBuilder(Object.class, Object.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(100, EntryUnit.ENTRIES))
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(60)))
.build());
#Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
createIfNotExists(cm, "model.Role");
createIfNotExists(cm, "model.User.roles");
// ...
};
}
private void createIfNotExists(CacheManager cacheManager, String cacheName) {
if (cacheManager.getCache(cacheName) == null) {
cacheManager.createCache(cacheName, jcacheConfiguration);
}
}
}
Gradle dependencies:
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache'
implementation group: 'javax.cache', name: 'cache-api'
implementation group: 'org.ehcache', name: 'ehcache'
implementation group: 'org.hibernate', name: 'hibernate-jcache'
The failing test:
#SpringBootTest
#RunWith(SpringJUnit4ClassRunner.class)
public class SecondLevelCacheTest {
#Autowired
private RoleRepository roleRepository;
private CacheManager manager;
#Before
public void initCacheManager() {
CachingProvider provider = Caching.getCachingProvider();
manager = provider.getCacheManager();
final String cacheRegion = "model.Role";
manager.getCache(cacheRegion).clear();
}
#Test
public final void givenEntityIsLoaded_thenItIsCached() {
final String cacheRegion = "model.Role";
boolean hasNext = manager.getCache(cacheRegion).iterator().hasNext();
final Role role = roleRepository.findByName("USER");
boolean hasNext2 = manager.getCache(cacheRegion).iterator().hasNext();
final Role role2 = roleRepository.findByName("USER");
Assert.assertFalse(hasNext);
Assert.assertTrue(hasNext2);
}
}
The most upvoted solution is "to set shared property to false in the testing context." How can I do this with regard to my configuration?

One option is to provide a custom CachingProvider that does not share CacheManager-s. An example solution can be found here.

Annotate your failing test class with #AutoConfigureCache. By default, this annotation will install a NoOpCacheManager, i.e. a basic, no operation CacheManager implementation suitable for disabling caching, typically used for backing cache declarations without an actual backing store. It will simply accept any items into the cache not actually storing them.
Spring Documentation on #AutoConfigureCache
Spring Documentation on #NoOpCacheManager

The proposed solution you are talking about is based on Ehcache 2. You are using Ehcache 3 (good for you), so it's not valid.
Spring will take care of closing the CacheManager so normally, you don't need to take care of anything. Also, you do not need to access it through the CachingProvider. You can #Autowired the javax.cache.CacheManager and that way you are sure to get the right one.
However, you are using Hibernate. You should make sure that Spring and Hibernate are using the same CacheManager. The way to configure it depends on the Spring and Hibernate version.
Can we have the full stack trace? Right now it feels like something is closing the CacheManager without deregister it from the CachingProvider. This is impossible unless you are closing the org.ehcache.CacheManagerwithout closing the javax.cache.CacheManager wrapping it. Closing the later will cause the deregistration.

Related

Spring - generic superclass not instantiated properly?

ATM I am in the middle of refactoring our Selenium E2E Test Framework to use Spring.
My class/bean:
package info.fingo.selenium.utils.driver;
#Component
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class ProxyDecorator extends WebDriverDecorator<WebDriver> {
#Autowired
public ProxyDecorator(TestUtils testUtils, DriverManager driverManager) {
super(WebDriver.class);
this.testUtils = testUtils;
this.driverManager = driverManager;
Superclass:
package org.openqa.selenium.support.decorators;
public class WebDriverDecorator<T extends WebDriver> {
private final Class<T> targetWebDriverClass;
private Decorated<T> decorated;
#SuppressWarnings("unchecked")
public WebDriverDecorator() {
this((Class<T>) WebDriver.class);
}
public WebDriverDecorator(Class<T> targetClass) {
this.targetWebDriverClass = targetClass;
}
public final T decorate(T original) {
Require.nonNull("WebDriver", original);
decorated = createDecorated(original);
return createProxy(decorated, targetWebDriverClass);
}
Issue occures on calling this line:
createProxy(decorated, targetWebDriverClass)
Where targetWebDriverClass for unknown reason is null and NullPointerException is later thrown.
This should not EVER happen as targetWebDriverClass is ALWAYS set through constructor - either provided by client (calling super(class)) or defaulted to WebDriver.class in default WebDriverDecorator constructor. Worked fine without Spring, and unfortunately I don't understand Spring enough to get any information through debugging.
My Spring dependencies:
ext.springVersion = '2.7.1'
dependencies {
//SPRING BOOT
api "org.springframework.boot:spring-boot-starter:$springVersion",
"org.springframework.boot:spring-boot-starter-aop:$springVersion",
"org.springframework.boot:spring-boot-starter-test:$springVersion",
decorate method in superclass WebDriverDecorator in marked as final which makes it ineligible for Spring CGLIB proxying as it cannot proxy final methods (& classes) - Sorry, I don't know exact reason why this caused my issue.
This is not my own class, it is taken from inside of dependency so I cannot change this.
This means that this class cannot be managed by Spring. In order for this to somehow work I get rid of inheritance (extends keyword) and replace it with composition. Got to do some reflection magic (for one of its protected method) but this seems to do the trick.

Spring Boot / Junit5+Mockito: Mock's mockitoInterceptor gets replaced during test?

I have two service classes (there are more, of course, but those two are relevant here), which are in use during an integration test.
For test, I set up a mock (ConfigurationService) and stub two methods:
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
#SpringBootTest
#TestPropertySource(properties =
"spring.main.allow-bean-definition-overriding=true")
public class VehicleAuditExecutionServiceIT {
#MockBean
private ConfigurationService configurationServiceMock;
#Autowired
private VehicleAuditExecutionService vehicleAuditExecutionService;
#Test
void testExecuteVehicleAudits() throws IOException {
// quite some DB operations for the test setup here
AuditDurationConfigDTO auditDurationConfigDTO = new AuditDurationConfigDTO();
auditDurationConfigDTO.setMaxDuration(deMaxDuration);
Map<String, ScheduledAuditConfigDTO> map = new HashMap<>();
map.put(country, ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.build());
when(configurationServiceMock.getAuditDurationConfig(country)).thenReturn(auditDurationConfigDTO);
when(configurationServiceMock.getScheduledAuditConfigurationForCountry(country)).thenReturn(ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.sameAuditDurationAllDealersSameGroupId(false)
.build());
vehicleAuditExecutionService.executeVehicleAudits(startDate, country);
verify(publishAuditInterfaceMock).pushExecutions(country, dealerDe1ExportAuditDtoList, auditCategory);
}
#ComponentScan(basePackages = "com.application")
#SpringBootApplication
#PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}
After the setup, the stubbings are available:
During the test's execution, vehicleAuditExecutionService.executeVehicleAudits(startDate, country) calls the AuditPreparationService, which in turn uses the configurationServiceMock (using #Autowired constructor injection). As expected, the calls gets matched and result set up is returned.
Later, the execution returns to vehicleAuditExecutionService.executeVehicleAudits(startDate, country), where it calls the configurationServiceMock (#Autowired constructor injection, as well) again. But here, the mock's configuration has been changed: the mock's attribute mockitoInterceptor gets replaced by some other instance.
Result: the stubbing is gone and the call returns null - leading to a NPE.
The screenshots were taken using org.springframework.boot:spring-boot-starter-parent:2.7.6, but I've tried that with multiple Spring Boot versions:
2.7.0
2.6.14
2.5.14
2.4.13
2.3.12.RELEASE
Each version has this issue - but I've never seen it in any other test. So I guess, there's something wrong with my test setup - but I cannot spot it.
Any idea, why this is happening?
Thanks a lot - please do not hesitate to ask for any further information, if needed for analysis.
kniffte

#Cacheable testing over method

I have a #Cacheable method inside a class.
I try to create that cache after a first call to that method, then, the second call should't go inside the method getCacheLeads.
#Service
public class LeadService {
#Autowired
private LeadRepository leadRepository;
#Autowired
public LeadService(LeadRepository leadRepository) {
this.leadRepository = leadRepository;
}
public void calculateLead(Lead leadBean) {
Lead lead = this.getCacheLeads(leadBean);
}
#Cacheable(cacheNames="leads", key="#leadBean.leadId")
public Lead getCacheLeads(Lead leadBean){
Lead result = leadRepository.findByLeadId(leadBean.getLeadId());
***logic to transform de Lead object***
return result;
}
}
But during testing that cache is never used, calling it twice with same parameter (serviceIsCalled) to ensure it is called twice to check it.
#ExtendWith(SpringExtension.class)
public class LeadServiceTest {
private LeadService leadService;
#Mock
private LeadRepository leadRepository;
#Autowired
CacheManager cacheManager;
#BeforeEach
public void setUp(){
leadService = new LeadService(leadRepository);
}
#Configuration
#EnableCaching
static class Config {
#Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("leads");
}
}
#Test
public void testLead(){
givenData();
serviceIsCalled();
serviceIsCalled();
checkDataArray();
}
private void givenData() {
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
Mockito.when(leadRepository.findByLeadId(any()))
.thenReturn(lead);
}
private void serviceIsCalled(){
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
leadService.calculateLead(lead);
}
private void checkDataArray(){
verify(leadRepository, times(1)).findByLeadId(anyString());
}
}
Why is it called 2 times?
You have a lot of things going on here, and someone looking at this and answering your question would definitely have to read between the lines.
First, your Spring configuration is not even correct. You are declaring the names of all the caches used by your Spring application (and tests) "statically" with the use of the ConcurrentMapCacheManager constructor accepting an array of cache names as the argument.
NOTE: Caches identified explicitly by name, and only these caches, are available at runtime.
#Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("LEAD_DATA");
}
In this case, your 1 and only cache is called "LEAD_DATA".
NOTE: Only the no arg constructor in `ConcurrentMapCacheManager allows dynamically created caches by name at runtime.
But then, in your #Service LeadService class, #Cacheable getCacheLeads(:Lead) method, you declare the cache to use as "leads".
#Service
public class LeadService {
#Cacheable(cacheNames="leads", key="#leadBean.leadId")
public Lead getCacheLeads(Lead leadBean){
// ...
}
}
This miss configuration will actually lead to an Exception at runtime similar to the following:
java.lang.IllegalArgumentException: Cannot find cache named 'leads' for Builder[public io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService.load(io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead)] caches=[leads] | key='#lead.id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
at org.springframework.cache.interceptor.AbstractCacheResolver.resolveCaches(AbstractCacheResolver.java:92)
at org.springframework.cache.interceptor.CacheAspectSupport.getCaches(CacheAspectSupport.java:252)
at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.<init>(CacheAspectSupport.java:724)
at org.springframework.cache.interceptor.CacheAspectSupport.getOperationContext(CacheAspectSupport.java:265)
at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContexts.<init>(CacheAspectSupport.java:615)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
at io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService$$EnhancerBySpringCGLIB$$86664246.load(<generated>)
...
..
.
Additionally, I don't see anything "outside" of the LeadsService bean calling the #Cacheable, getCacheLeads(..) method. Inside your test, you are calling:
leadService.calculateLead(lead);
As follows:
private void serviceIsCalled(){
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
leadService.calculateLead(lead);
}
If the calculateLead(:Lead) LeadService method is calling the #Cacheable, getCacheLeads(:Lead) LeadService method (internally), then that is not going to cause the caching functionality to kick in since you are already "behind" the AOP Proxy setup by Spring to "enable" caching behavior for your LeadService bean.
See the Spring Framework AOP documentation on this matter.
NOTE: Spring's Cache Abstraction, like the Spring's Transaction Management, is built on the Spring AOP infrastructure, as are many other things in Spring.
In your case this means:
Test -> <PROXY> -> LeadService.calculateLead(:Lead) -> LeadService.getCacheLeads(:Lead)
However, between LeadSevice.calculateLead(:Lead) and LeadService.getCacheLeads(:Lead), NO PROXY is involved, therefore Spring's caching behavior will not be applied.
Only...
Test (or some other bean) -> <PROXY> -> LeadService.getCacheLeads(:Lead)
Will result in the AOP Proxy decorated with the Caching Interceptors being invoked and the caching behavior applied.
You can see that your use case will work correctly when configured and used correctly as demonstrated in my example test class, modeled after your domain.
Look for the comments that explain why your configuration will fail in your case.

Caused by: com.datastax.oss.driver.api.core.InvalidKeyspaceException: Invalid keyspace mykeyspace in Spring Boot Cassandra

I devised a project with the usage of Spring boot and Cassandra which runs on Docker Container.
After I implemented the configuration of Cassandra, I ran the project and it threw an error shown below.
Caused by: com.datastax.oss.driver.api.core.InvalidKeyspaceException: Invalid keyspace mykeyspace
How can I fix the issue?
Here is my application.properties file.
spring.cassandra.contactpoints=127.0.0.1
spring.cassandra.port=9042
spring.data.cassandra.keyspace-name=mykeyspace
spring.cassandra.basepackages=com.springboot.cassandra
Here is a configuration file of Cassandra
#Configuration
#EnableCassandraRepositories
public class CassandraConfiguration extends AbstractCassandraConfiguration {
#Value("${spring.cassandra.contactpoints}")
private String contactPoint;
#Value("${spring.cassandra.port}")
private int port;
#Value("${spring.data.cassandra.keyspace-name}")
private String keyspaceName;
#Value("${spring.cassandra.basepackages}")
private String basePackages;
#Override
protected String getKeyspaceName() {
return keyspaceName;
}
#Override
protected int getPort() {
return port;
}
#Override
protected String getContactPoints() {
return contactPoint;
}
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
#Override
public String[] getEntityBasePackages() {
return new String[] {basePackages};
}
}
By default Spring Data will not create or alter the schema for you. This is a good thing for most use cases as normally you do not want your schema to be created based on a java class. Altering would be even worse in general and also difficult for Cassandra in particular.
If you want for spring to create it you need:
spring.data.cassandra.schema-action=CREATE_IF_NOT_EXISTS
I would still recommend not using this in production.
When talking about keyspaces however, from my knowledge and from the wording of the documentation spring will not create the keyspace even if you use the code above. This makes a lot of sense for Cassandra as a keyspace needs info such as replication strategy and replication factor, the latter being changed for things such as new Data centers being added or removed. This things are administrative tasks that should not be left up to Spring.
If you can delete - character, I think you can run code without errors.
Replace this line:
#Value("${spring.data.cassandra.keyspace-name}")
with this one:
#Value("${spring.data.cassandra.keyspacename}")
With spring-boot there's no need to extend AbstractCassandraConfiguration, it can auto-configure Cassandra for you. You can just remove your configuration class and everything will work just fine. However, even in this case you need to do some work to automatically create the keyspace. I've settled on an auto-configuration added to our company's spring-boot starter, but you can also define it as a regular configuration in your application.
/**
* create the configured keyspace before the first cqlSession is instantiated. This is guaranteed by running this
* autoconfiguration before the spring-boot one.
*/
#ConditionalOnClass(CqlSession.class)
#ConditionalOnProperty(name = "spring.data.cassandra.create-keyspace", havingValue = "true")
#AutoConfigureBefore(CassandraAutoConfiguration.class)
public class CassandraCreateKeyspaceAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(CassandraCreateKeyspaceAutoConfiguration.class);
public CassandraCreateKeyspaceAutoConfiguration(CqlSessionBuilder cqlSessionBuilder, CassandraProperties properties) {
// It's OK to mutate cqlSessionBuilder because it has prototype scope.
try (CqlSession session = cqlSessionBuilder.withKeyspace((CqlIdentifier) null).build()) {
logger.info("Creating keyspace {} ...", properties.getKeyspaceName());
session.execute(CreateKeyspaceCqlGenerator.toCql(
CreateKeyspaceSpecification.createKeyspace(properties.getKeyspaceName()).ifNotExists()));
}
}
}
In my case I've also added a configuration property to control the creation, spring.data.cassandra.create-keyspace, you may leave it out if you don't need the flexibility.
Note that spring-boot auto-configuration depends on certain configuration properties, here's what I have in my dev environment:
spring:
data:
cassandra:
keyspace-name: mykeyspace
contact-points: 127.0.0.1
port: 9042
local-datacenter: datacenter1
schema-action: CREATE_IF_NOT_EXISTS
create-keyspace: true
More details: spring-boot and Cassandra

Configuration for specific Caffeine Caches in Spring

We need to implement several methods that have different caching times. Each method is annotated with #Cacheable and our current solution includes multiple CacheManager that are set in a CachingConfigurerSupport.
public class CachingConfiguration extends CachingConfigurerSupport {
#Override
#Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.DAYS));
return cacheManager;
}
#Bean
public CacheManager anotherCache() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES));
return cacheManager;
}
}
The #Cacheable annotation then included the cacheManager:
#Cacheable(cacheNames = "someCache", cacheManager = "anotherCache")
Basically that's fine but is also errorprune if you forget the cacheManager parameter etc.
So I currently try to find a better solution but as far as I can see, there is currently no general accepted way to go.
Imho the main advantage of the CaffeineCacheManager compared to e.g. SimpleCacheManager is the possibility to define a base configuration and then initialize additional caches lazily. But wouldn't it be great if you are able to set additional caches which are never reinitialized?
Those caches must be used preferentially and created in the CachingConfigurerSupport.
Maybe I'm missing something, but shouldn't this solve the problem that has already been discussed in several threads in different forms?
Recently I decided to turn my initial PR into a separate tiny project.
To start using it just add the latest dependency from Maven Central:
<dependency>
<groupId>io.github.stepio.coffee-boots</groupId>
<artifactId>coffee-boots</artifactId>
<version>2.0.0</version>
</dependency>
Format of properties is the following:
coffee-boots.cache.spec.myCache=maximumSize=100000,expireAfterWrite=1m
If no specific configuration is defined, CacheManager defaults to Spring's behavior.
Simple way without any thrid-party lib:
spring.cache.type=caffeine
# default spec (optional)
spring.cache.caffeine.spec=maximumSize=250,expireAfterWrite=15m
# specific specs (also optional)
caffeine.specs.places=maximumSize=1000,expireAfterWrite=1h
Register custom caches:
applicationContext.registerBean { context ->
CacheManagerCustomizer<CaffeineCacheManager> { cacheManager ->
for (spec in Binder.get(context.environment).bindOrCreate("caffeine.specs", HashMap::class.java)) {
cacheManager.registerCustomCache(spec.key.toString(), Caffeine.from(spec.value.toString()).build())
}
}
}

Resources