Skip to content

Commit 45a97bb

Browse files
committed
GP-67 Add legacy exercise
1 parent a669240 commit 45a97bb

File tree

17 files changed

+618
-0
lines changed

17 files changed

+618
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Hello ApplicationContext exercise :muscle:
2+
Improve your *Spring ApplicationContext* Java configuration skills
3+
### Task
4+
The task is to **configure `ApplicationContext`** that contains `AccountService` bean, `AccountDao` bean
5+
and `TestDataGenerator` bean. Your job is to follow the instructions in the *todo* section and **implement
6+
a proper configuration.**
7+
8+
To verify your configuration, run `AppConfigTest.java`
9+
10+
11+
### Pre-conditions :heavy_exclamation_mark:
12+
You're supposed to be familiar with *Spring IoC* and *Dependency injection*
13+
14+
### How to start :question:
15+
* Just clone the repository and start implementing the **todo** section, verify your changes by running tests
16+
* If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source)
17+
* Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation
18+
19+
### Related materials :information_source:
20+
* [Spring IoC basics tutorial](https://github.com/boy4uck/spring-framework-tutorial/tree/master/spring-framework-ioc-basics)<img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=20/>
21+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>3-0-spring-framework</artifactId>
7+
<groupId>com.bobocode</groupId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>3-0-0-hello-spring-framework</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>org.springframework</groupId>
17+
<artifactId>spring-context</artifactId>
18+
<version>5.0.7.RELEASE</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.springframework</groupId>
22+
<artifactId>spring-test</artifactId>
23+
<version>5.0.7.RELEASE</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.hamcrest</groupId>
27+
<artifactId>hamcrest-all</artifactId>
28+
<version>1.3</version>
29+
<scope>test</scope>
30+
</dependency>
31+
<dependency>
32+
<groupId>com.bobocode</groupId>
33+
<artifactId>spring-framework-exercises-util</artifactId>
34+
<version>1.0-SNAPSHOT</version>
35+
</dependency>
36+
</dependencies>
37+
38+
</project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.bobocode.config;
2+
3+
import com.bobocode.TestDataGenerator;
4+
5+
/**
6+
* This class application context configuration.
7+
* <p>
8+
* todo: make this class a Spring configuration class
9+
* todo: enable component scanning for dao and service packages
10+
* todo: provide explicit configuration for a bean of type {@link TestDataGenerator} with name "dataGenerator" in this class.
11+
* todo: Don't specify bean name "dataGenerator" explicitly
12+
*/
13+
public class AppConfig {
14+
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.bobocode.dao;
2+
3+
import com.bobocode.model.Account;
4+
5+
import java.util.List;
6+
7+
/**
8+
* Defines an API for {@link Account} data access object (DAO).
9+
*/
10+
public interface AccountDao {
11+
List<Account> findAll();
12+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.bobocode.dao;
2+
3+
import com.bobocode.TestDataGenerator;
4+
import com.bobocode.model.Account;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
7+
import java.util.List;
8+
import java.util.stream.Stream;
9+
10+
import static java.util.stream.Collectors.toList;
11+
12+
/**
13+
* Provides a fake {@link AccountDao} implementation that uses generated fake data.
14+
* <p>
15+
* todo: configure this class as Spring component with bean name "accountDao"
16+
* todo: use explicit (with {@link Autowired} annotation) constructor-based dependency injection
17+
*/
18+
public class FakeAccountDao implements AccountDao {
19+
private List<Account> accounts;
20+
21+
public FakeAccountDao(TestDataGenerator testDataGenerator) {
22+
this.accounts = Stream.generate(testDataGenerator::generateAccount)
23+
.limit(20)
24+
.collect(toList());
25+
}
26+
27+
@Override
28+
public List<Account> findAll() {
29+
return accounts;
30+
}
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.bobocode.service;
2+
3+
import com.bobocode.dao.AccountDao;
4+
import com.bobocode.model.Account;
5+
6+
import java.util.Comparator;
7+
import java.util.List;
8+
9+
/**
10+
* Provides service API for {@link Account}.
11+
* <p>
12+
* todo: configure {@link AccountService} bean implicitly using special annotation for service classes
13+
* todo: use implicit constructor-based dependency injection (don't use {@link org.springframework.beans.factory.annotation.Autowired})
14+
*/
15+
public class AccountService {
16+
private final AccountDao accountDao;
17+
18+
public AccountService(AccountDao accountDao) {
19+
this.accountDao = accountDao;
20+
}
21+
22+
public Account findRichestAccount() {
23+
List<Account> accounts = accountDao.findAll();
24+
return accounts.stream()
25+
.max(Comparator.comparing(Account::getBalance))
26+
.get();
27+
}
28+
29+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package com.bobocode;
2+
3+
import com.bobocode.config.AppConfig;
4+
import com.bobocode.dao.AccountDao;
5+
import com.bobocode.dao.FakeAccountDao;
6+
import com.bobocode.model.Account;
7+
import com.bobocode.service.AccountService;
8+
import org.junit.jupiter.api.Test;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.context.ApplicationContext;
11+
import org.springframework.context.annotation.Bean;
12+
import org.springframework.context.annotation.ComponentScan;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.stereotype.Component;
15+
import org.springframework.stereotype.Service;
16+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
17+
18+
import java.lang.annotation.Annotation;
19+
import java.lang.reflect.Method;
20+
import java.util.Comparator;
21+
import java.util.Map;
22+
23+
import static org.hamcrest.MatcherAssert.assertThat;
24+
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
25+
import static org.hamcrest.Matchers.arrayWithSize;
26+
import static org.hamcrest.Matchers.equalTo;
27+
import static org.hamcrest.Matchers.hasItem;
28+
import static org.hamcrest.Matchers.is;
29+
import static org.hamcrest.Matchers.notNullValue;
30+
31+
32+
@SpringJUnitConfig
33+
class AppConfigTest {
34+
@Configuration
35+
@ComponentScan(basePackages = "com.bobocode")
36+
static class TestConfig {
37+
}
38+
39+
@Autowired
40+
private ApplicationContext applicationContext;
41+
42+
@Autowired
43+
private AccountService accountService;
44+
45+
@Autowired
46+
private AccountDao accountDao;
47+
48+
@Test
49+
void testConfigClassIsMarkedAsConfiguration() {
50+
Configuration configuration = AppConfig.class.getAnnotation(Configuration.class);
51+
52+
assertThat(configuration, notNullValue());
53+
}
54+
55+
@Test
56+
void testComponentScanIsEnabled() {
57+
ComponentScan componentScan = AppConfig.class.getAnnotation(ComponentScan.class);
58+
59+
assertThat(componentScan, notNullValue());
60+
}
61+
62+
@Test
63+
void testComponentScanPackagesAreSpecified() {
64+
ComponentScan componentScan = AppConfig.class.getAnnotation(ComponentScan.class);
65+
String[] packages = componentScan.basePackages();
66+
if (packages.length == 0) {
67+
packages = componentScan.value();
68+
}
69+
assertThat(packages, arrayContainingInAnyOrder("com.bobocode.dao", "com.bobocode.service"));
70+
}
71+
72+
@Test
73+
void testDataGeneratorHasOnlyOneBean() {
74+
Map<String, TestDataGenerator> testDataGeneratorMap = applicationContext.getBeansOfType(TestDataGenerator.class);
75+
76+
assertThat(testDataGeneratorMap.size(), is(1));
77+
}
78+
79+
@Test
80+
void testDataGeneratorBeanIsConfiguredExplicitly() {
81+
Method[] methods = AppConfig.class.getMethods();
82+
Method testDataGeneratorBeanMethod = findTestDataGeneratorBeanMethod(methods);
83+
84+
85+
assertThat(testDataGeneratorBeanMethod, notNullValue());
86+
}
87+
88+
@Test
89+
void testDataGeneratorBeanName() {
90+
Map<String, TestDataGenerator> dataGeneratorBeanMap = applicationContext.getBeansOfType(TestDataGenerator.class);
91+
92+
assertThat(dataGeneratorBeanMap.keySet(), hasItem("dataGenerator"));
93+
}
94+
95+
@Test
96+
void testDataGeneratorBeanNameIsNotSpecifiedExplicitly() {
97+
Method[] methods = AppConfig.class.getMethods();
98+
Method testDataGeneratorBeanMethod = findTestDataGeneratorBeanMethod(methods);
99+
Bean bean = testDataGeneratorBeanMethod.getDeclaredAnnotation(Bean.class);
100+
101+
assertThat(bean.name(), arrayWithSize(0));
102+
assertThat(bean.value(), arrayWithSize(0));
103+
}
104+
105+
private Method findTestDataGeneratorBeanMethod(Method[] methods) {
106+
for (Method method : methods) {
107+
if (method.getReturnType().equals(TestDataGenerator.class)
108+
&& method.getDeclaredAnnotation(Bean.class) != null) {
109+
return method;
110+
}
111+
}
112+
return null;
113+
}
114+
115+
@Test
116+
void testFakeAccountDaoIsConfiguredAsComponent() {
117+
Component component = FakeAccountDao.class.getAnnotation(Component.class);
118+
119+
assertThat(component, notNullValue());
120+
}
121+
122+
@Test
123+
void testAccountDaoHasOnlyOneBean() {
124+
Map<String, AccountDao> accountDaoBeanMap = applicationContext.getBeansOfType(AccountDao.class);
125+
126+
assertThat(accountDaoBeanMap.size(), is(1));
127+
}
128+
129+
@Test
130+
void testAccountDaoBeanName() {
131+
Map<String, AccountDao> accountDaoBeanMap = applicationContext.getBeansOfType(AccountDao.class);
132+
133+
assertThat(accountDaoBeanMap.keySet(), hasItem("accountDao"));
134+
}
135+
136+
@Test
137+
void testAccountDaoConstructorIsMarkedWithAutowired() throws NoSuchMethodException {
138+
Autowired autowired = FakeAccountDao.class.getConstructor(TestDataGenerator.class).getAnnotation(Autowired.class);
139+
140+
assertThat(autowired, notNullValue());
141+
}
142+
143+
@Test
144+
void testAccountServiceHasOnlyOneBean() {
145+
Map<String, AccountService> accountServiceMap = applicationContext.getBeansOfType(AccountService.class);
146+
147+
assertThat(accountServiceMap.size(), is(1));
148+
}
149+
150+
@Test
151+
void testAccountServiceIsConfiguredAsService() {
152+
Service service = AccountService.class.getAnnotation(Service.class);
153+
154+
assertThat(service, notNullValue());
155+
}
156+
157+
@Test
158+
void testAccountServiceBeanName() {
159+
Map<String, AccountService> accountServiceMap = applicationContext.getBeansOfType(AccountService.class);
160+
161+
assertThat(accountServiceMap.keySet(), hasItem("accountService"));
162+
}
163+
164+
@Test
165+
void testAccountServiceBeanNameIsNotSpecifiedExplicitly() {
166+
Service service = AccountService.class.getAnnotation(Service.class);
167+
168+
assertThat(service.value(), equalTo(""));
169+
}
170+
171+
@Test
172+
void testAccountServiceDoesNotUseAutowired() throws NoSuchMethodException {
173+
Annotation[] annotations = AccountService.class.getConstructor(AccountDao.class).getDeclaredAnnotations();
174+
175+
assertThat(annotations, arrayWithSize(0));
176+
}
177+
178+
@Test
179+
void testFindRichestAccount() {
180+
Account richestAccount = accountService.findRichestAccount();
181+
182+
Account actualRichestAccount = accountDao.findAll().stream().max(Comparator.comparing(Account::getBalance)).get();
183+
184+
assertThat(richestAccount, equalTo(actualRichestAccount));
185+
}
186+
187+
188+
}

3-0-spring-framework/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<packaging>pom</packaging>
1414

1515
<modules>
16+
<module>3-0-0-hello-spring-framework</module>
1617
<module>3-0-1-hello-spring-mvc</module>
1718
</modules>
1819

java-web-course-util/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
<version>1.0-SNAPSHOT</version>
99
</parent>
1010
<modelVersion>4.0.0</modelVersion>
11+
<packaging>pom</packaging>
1112

1213
<artifactId>java-web-course-util</artifactId>
1314

15+
<modules>
16+
<module>spring-framework-exercises-model</module>
17+
<module>spring-framework-exercises-util</module>
18+
</modules>
19+
1420
</project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>java-web-course-util</artifactId>
7+
<groupId>com.bobocode</groupId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
<packaging>pom</packaging>
12+
13+
<artifactId>spring-framework-exercises-model</artifactId>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>org.hibernate.javax.persistence</groupId>
18+
<artifactId>hibernate-jpa-2.1-api</artifactId>
19+
<version>1.0.2.Final</version>
20+
</dependency>
21+
</dependencies>
22+
23+
</project>

0 commit comments

Comments
 (0)