Skip to content

Commit a1552d5

Browse files
authored
Fix repositories being attacked multiple times per call (#567)
2 parents b47b5e8 + 283b556 commit a1552d5

File tree

17 files changed

+374
-23
lines changed

17 files changed

+374
-23
lines changed

chaos-monkey-docs/src/main/asciidoc/changes.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Built with Spring Boot {spring-boot-version}
55

66
=== Bug Fixes
77
// - https://github.com/codecentric/chaos-monkey-spring-boot/pull/xxx[#xxx] Added example entry. Please don't remove.
8+
- https://github.com/codecentric/chaos-monkey-spring-boot/pull/567[#567] Fix deterministic attacks on Spring Data repositories not working correctly
89

910
=== Improvements
1011
// - https://github.com/codecentric/chaos-monkey-spring-boot/pull/xxx[#xxx] Added example entry. Please don't remove.
@@ -16,5 +17,9 @@ Built with Spring Boot {spring-boot-version}
1617
This release was only possible because of these great humans ❤️:
1718

1819
// - https://github.com/octocat[@octocat]
20+
- https://github.com/octocat[@denniseffing]
21+
- https://github.com/octocat[@WtfJoke]
22+
- https://github.com/octocat[@jens-kaiser]
23+
- https://github.com/octocat[@pratik-chauhan]
1924

2025
Thank you for your support!

chaos-monkey-spring-boot/src/main/java/de/codecentric/spring/boot/chaos/monkey/configuration/ChaosMonkeyAdvisorConfiguration.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022-2023 the original author or authors.
2+
* Copyright 2022-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,9 +26,11 @@
2626
import de.codecentric.spring.boot.chaos.monkey.watcher.advice.filter.ChaosMonkeyBaseClassFilter;
2727
import de.codecentric.spring.boot.chaos.monkey.watcher.advice.filter.MethodNameFilter;
2828
import de.codecentric.spring.boot.chaos.monkey.watcher.advice.filter.RepositoryAnnotatedClassFilter;
29+
import de.codecentric.spring.boot.chaos.monkey.watcher.advice.filter.RepositoryClassFilter;
2930
import de.codecentric.spring.boot.chaos.monkey.watcher.advice.filter.SpringHookMethodsFilter;
3031
import lombok.RequiredArgsConstructor;
3132
import lombok.val;
33+
import org.springframework.aop.ClassFilter;
3234
import org.springframework.aop.support.ClassFilters;
3335
import org.springframework.aop.support.RootClassFilter;
3436
import org.springframework.aop.support.annotation.AnnotationClassFilter;
@@ -90,26 +92,17 @@ public ChaosMonkeyPointcutAdvisor componentPointcutAdvisor(ChaosMonkeyBaseClassF
9092
}
9193

9294
@Bean
93-
@ConditionalOnMissingBean(name = "jpaRepositoryPointcutAdvisor")
95+
@ConditionalOnMissingBean(name = "repositoryPointcutAdvisor")
9496
@ConditionalOnClass(name = "org.springframework.data.repository.Repository")
95-
public ChaosMonkeyPointcutAdvisor jpaRepositoryPointcutAdvisor(ChaosMonkeyBaseClassFilter baseClassFilter, ChaosMonkeyRequestScope requestScope,
97+
public ChaosMonkeyPointcutAdvisor repositoryPointcutAdvisor(ChaosMonkeyBaseClassFilter baseClassFilter, ChaosMonkeyRequestScope requestScope,
9698
MetricEventPublisher eventPublisher) throws ClassNotFoundException {
9799
@SuppressWarnings("unchecked")
98100
val repositoryDefinition = (Class<? extends Annotation>) Class.forName("org.springframework.data.repository.RepositoryDefinition");
99-
Class<?> repository = Class.forName("org.springframework.data.repository.Repository");
100-
return new ChaosMonkeyPointcutAdvisor(baseClassFilter,
101-
new ChaosMonkeyDefaultAdvice(requestScope, eventPublisher, ChaosTarget.REPOSITORY, watcherProperties::isRepository),
102-
ClassFilters.union(new AnnotationClassFilter(repositoryDefinition, false), new RootClassFilter(repository)),
103-
SpringHookMethodsFilter.INSTANCE);
104-
}
105101

106-
@Bean
107-
@ConditionalOnMissingBean(name = "jdbcRepositoryPointcutAdvisor")
108-
public ChaosMonkeyPointcutAdvisor jdbcRepositoryPointcutAdvisor(ChaosMonkeyBaseClassFilter baseClassFilter, ChaosMonkeyRequestScope requestScope,
109-
MetricEventPublisher eventPublisher) {
102+
ClassFilter[] filters = {new RepositoryClassFilter(), new RepositoryAnnotatedClassFilter(), new AnnotationClassFilter(repositoryDefinition)};
110103
return new ChaosMonkeyPointcutAdvisor(baseClassFilter,
111104
new ChaosMonkeyDefaultAdvice(requestScope, eventPublisher, ChaosTarget.REPOSITORY, watcherProperties::isRepository),
112-
new RepositoryAnnotatedClassFilter());
105+
ClassFilters.union(filters), SpringHookMethodsFilter.INSTANCE);
113106
}
114107

115108
@Bean
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.codecentric.spring.boot.chaos.monkey.watcher.advice.filter;
17+
18+
import jakarta.annotation.Nonnull;
19+
import org.springframework.aop.ClassFilter;
20+
21+
import java.lang.reflect.Proxy;
22+
23+
public class RepositoryClassFilter implements ClassFilter {
24+
private static final String SPRING_DATA_REPOSITORY_CLASS_REF = "org.springframework.data.repository.Repository";
25+
26+
private final Class<?> repositoryClass;
27+
28+
public RepositoryClassFilter() throws ClassNotFoundException {
29+
repositoryClass = Class.forName(SPRING_DATA_REPOSITORY_CLASS_REF);
30+
}
31+
32+
@Override
33+
public boolean matches(@Nonnull Class<?> clazz) {
34+
return Proxy.isProxyClass(clazz) && repositoryClass.isAssignableFrom(clazz);
35+
}
36+
37+
@Override
38+
public String toString() {
39+
return "RepositoryClassFilter{repositoryClass=" + SPRING_DATA_REPOSITORY_CLASS_REF + '}';
40+
}
41+
42+
@Override
43+
public final boolean equals(Object other) {
44+
return other instanceof RepositoryClassFilter;
45+
}
46+
47+
@Override
48+
public int hashCode() {
49+
return SPRING_DATA_REPOSITORY_CLASS_REF.hashCode();
50+
}
51+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2023-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.codecentric.spring.boot.chaos.monkey.watcher;
17+
18+
import de.codecentric.spring.boot.demo.chaos.monkey.ChaosDemoApplication;
19+
import de.codecentric.spring.boot.demo.chaos.monkey.repository.DemoRepository;
20+
import org.junit.jupiter.api.Test;
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.boot.test.context.SpringBootTest;
23+
import org.springframework.test.context.ActiveProfiles;
24+
25+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
26+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException;
27+
28+
@SpringBootTest(properties = {"chaos.monkey.watcher.repository=true", "chaos.monkey.assaults.exceptions-active=true",
29+
"chaos.monkey.assaults.deterministic=true", "chaos.monkey.assaults.level=2",
30+
"chaos.monkey.enabled=true"}, classes = {ChaosDemoApplication.class})
31+
@ActiveProfiles("chaos-monkey")
32+
public class ChaosMonkeyDeterministicRepositoryWatcherIntegrationTest {
33+
34+
@Autowired
35+
private DemoRepository demoRepository;
36+
37+
@Test
38+
public void shouldThrowExceptionOnEverySecondCall() {
39+
assertThatNoException().isThrownBy(() -> demoRepository.count());
40+
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> demoRepository.count());
41+
assertThatNoException().isThrownBy(() -> demoRepository.count());
42+
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> demoRepository.count());
43+
}
44+
}

chaos-monkey-spring-boot/src/test/java/de/codecentric/spring/boot/demo/chaos/monkey/repository/CrudDemoRepository.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2022 the original author or authors.
2+
* Copyright 2018-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,5 @@
1818
import org.springframework.data.repository.CrudRepository;
1919
import org.springframework.stereotype.Repository;
2020

21-
@Repository
2221
public interface CrudDemoRepository extends CrudRepository<Hello, Long> {
2322
}

chaos-monkey-spring-boot/src/test/java/de/codecentric/spring/boot/demo/chaos/monkey/repository/DemoRepository.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2022 the original author or authors.
2+
* Copyright 2018-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
1919
import org.springframework.stereotype.Repository;
2020

2121
/** @author Benjamin Wilms */
22-
@Repository
2322
public interface DemoRepository extends CrudRepository<Hello, Long> {
2423

2524
void dummyPublicSaveMethod();

chaos-monkey-spring-boot/src/test/java/de/codecentric/spring/boot/demo/chaos/monkey/repository/DemoRepositoryJDBC.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2022 the original author or authors.
2+
* Copyright 2018-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2018-2025 the original author or authors.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
18+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
19+
<modelVersion>4.0.0</modelVersion>
20+
21+
<parent>
22+
<groupId>de.codecentric</groupId>
23+
<artifactId>chaos-monkey-dependencies</artifactId>
24+
<version>${revision}</version>
25+
<relativePath>../../chaos-monkey-dependencies</relativePath>
26+
</parent>
27+
28+
<artifactId>chaos-monkey-demo-app-jdbc</artifactId>
29+
<description>Chaos Monkey for Spring Boot simple demo app with Spring Data JDBC</description>
30+
<name>chaos-monkey-demo-app</name>
31+
32+
<dependencies>
33+
<dependency>
34+
<groupId>de.codecentric</groupId>
35+
<artifactId>chaos-monkey-spring-boot</artifactId>
36+
<version>${revision}</version>
37+
</dependency>
38+
<dependency>
39+
<groupId>org.springframework.boot</groupId>
40+
<artifactId>spring-boot-starter-web</artifactId>
41+
<scope>compile</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-actuator</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-data-jdbc</artifactId>
50+
</dependency>
51+
<dependency>
52+
<groupId>com.h2database</groupId>
53+
<artifactId>h2</artifactId>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.springframework.boot</groupId>
57+
<artifactId>spring-boot-starter-test</artifactId>
58+
<scope>test</scope>
59+
</dependency>
60+
<dependency>
61+
<groupId>org.mockito</groupId>
62+
<artifactId>mockito-core</artifactId>
63+
<scope>test</scope>
64+
</dependency>
65+
<dependency>
66+
<groupId>org.springframework.boot</groupId>
67+
<artifactId>spring-boot-devtools</artifactId>
68+
<version>${spring-boot.version}</version>
69+
<optional>true</optional>
70+
</dependency>
71+
72+
</dependencies>
73+
74+
<build>
75+
<plugins>
76+
<plugin>
77+
<groupId>org.springframework.boot</groupId>
78+
<artifactId>spring-boot-maven-plugin</artifactId>
79+
<executions>
80+
<execution>
81+
<goals>
82+
<goal>repackage</goal>
83+
</goals>
84+
</execution>
85+
</executions>
86+
</plugin>
87+
</plugins>
88+
</build>
89+
</project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2018-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.chaos.monkey.chaosdemo;
17+
18+
import org.springframework.boot.SpringApplication;
19+
import org.springframework.boot.autoconfigure.SpringBootApplication;
20+
21+
@SpringBootApplication
22+
public class ChaosDemoApplication {
23+
24+
public static void main(String[] args) {
25+
SpringApplication.run(ChaosDemoApplication.class, args);
26+
}
27+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2018-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.chaos.monkey.chaosdemo;
17+
18+
import org.springframework.data.annotation.Id;
19+
20+
public record Person(@Id Long id, String name) {
21+
}

0 commit comments

Comments
 (0)