├── .gitignore
├── eureka-server
├── src
│ └── main
│ │ ├── resources
│ │ ├── application.properties
│ │ └── bootstrap.properties
│ │ └── java
│ │ └── example
│ │ └── infra
│ │ └── eureka
│ │ └── EurekaServerApplication.java
└── pom.xml
├── hystrix-dashboard
├── src
│ └── main
│ │ ├── resources
│ │ └── application.properties
│ │ └── java
│ │ └── example
│ │ └── infra
│ │ └── hystrix
│ │ └── HystrixDashboardApplication.java
└── pom.xml
├── store-app
├── src
│ ├── main
│ │ ├── resources
│ │ │ ├── application.properties
│ │ │ └── bootstrap.properties
│ │ └── java
│ │ │ └── example
│ │ │ └── store
│ │ │ ├── StoreApplication.java
│ │ │ ├── domain
│ │ │ ├── StoreRepository.java
│ │ │ ├── Store.java
│ │ │ └── Address.java
│ │ │ └── StoreInitializer.java
│ └── test
│ │ ├── resources
│ │ └── application-test.properties
│ │ └── java
│ │ └── example
│ │ └── store
│ │ └── domain
│ │ └── StoreRepositoryIntegrationTest.java
└── pom.xml
├── customer-app
├── src
│ ├── main
│ │ ├── resources
│ │ │ ├── application.properties
│ │ │ └── bootstrap.properties
│ │ └── java
│ │ │ └── example
│ │ │ └── customer
│ │ │ ├── domain
│ │ │ ├── CustomerRepository.java
│ │ │ ├── Location.java
│ │ │ ├── Customer.java
│ │ │ └── Address.java
│ │ │ ├── integration
│ │ │ └── store
│ │ │ │ ├── Store.java
│ │ │ │ └── StoreService.java
│ │ │ ├── web
│ │ │ └── CustomerController.java
│ │ │ └── CustomerApplication.java
│ └── test
│ │ ├── resources
│ │ └── application-test.properties
│ │ └── java
│ │ └── example
│ │ └── customer
│ │ └── domain
│ │ └── CustomerRepositoryIntegrationTest.java
└── pom.xml
├── config-server
├── src
│ └── main
│ │ ├── resources
│ │ └── application.properties
│ │ └── java
│ │ └── example
│ │ └── infra
│ │ └── config
│ │ └── ConfigServerApplication.java
└── pom.xml
├── README.adoc
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 | target
4 |
--------------------------------------------------------------------------------
/eureka-server/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=8761
--------------------------------------------------------------------------------
/hystrix-dashboard/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=7979
--------------------------------------------------------------------------------
/store-app/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=8081
2 |
3 |
--------------------------------------------------------------------------------
/customer-app/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=8080
2 |
3 |
--------------------------------------------------------------------------------
/store-app/src/test/resources/application-test.properties:
--------------------------------------------------------------------------------
1 | eureka.client.enabled=false
--------------------------------------------------------------------------------
/customer-app/src/test/resources/application-test.properties:
--------------------------------------------------------------------------------
1 | eureka.client.enabled=false
--------------------------------------------------------------------------------
/store-app/src/main/resources/bootstrap.properties:
--------------------------------------------------------------------------------
1 | spring.application.name=store-app
2 |
3 | spring.cloud.config.uri=${CONFIG_SERVER_URI:http://localhost:8888}
--------------------------------------------------------------------------------
/customer-app/src/main/resources/bootstrap.properties:
--------------------------------------------------------------------------------
1 | spring.application.name=customer-app
2 |
3 | spring.cloud.config.uri=${CONFIG_SERVER_URI:http://localhost:8888}
--------------------------------------------------------------------------------
/eureka-server/src/main/resources/bootstrap.properties:
--------------------------------------------------------------------------------
1 | spring.application.name=eureka-server
2 |
3 | spring.cloud.config.uri=${CONFIG_SERVER_URI:http://localhost:8888}
--------------------------------------------------------------------------------
/config-server/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=8888
2 |
3 | spring.cloud.config.server.git.uri=https://github.com/snicoll-demos/spring-cloud-intro-conference-config.git
4 | spring.cloud.config.server.default-label=step3
5 |
--------------------------------------------------------------------------------
/config-server/src/main/java/example/infra/config/ConfigServerApplication.java:
--------------------------------------------------------------------------------
1 | package example.infra.config;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.config.server.EnableConfigServer;
6 |
7 | /**
8 | * Start a config server.
9 | *
10 | * @author Stephane Nicoll
11 | */
12 | @SpringBootApplication
13 | @EnableConfigServer
14 | public class ConfigServerApplication {
15 |
16 | public static void main(String[] args) {
17 | SpringApplication.run(ConfigServerApplication.class, args);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/eureka-server/src/main/java/example/infra/eureka/EurekaServerApplication.java:
--------------------------------------------------------------------------------
1 | package example.infra.eureka;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
6 |
7 | /**
8 | * Start a Eureka server.
9 | *
10 | * @author Stephane Nicoll
11 | */
12 | @SpringBootApplication
13 | @EnableEurekaServer
14 | public class EurekaServerApplication {
15 |
16 | public static void main(String[] args) {
17 | SpringApplication.run(EurekaServerApplication.class, args);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/hystrix-dashboard/src/main/java/example/infra/hystrix/HystrixDashboardApplication.java:
--------------------------------------------------------------------------------
1 | package example.infra.hystrix;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
6 |
7 | /**
8 | * Start the hystrix dashboard to monitor links between apps.
9 | *
10 | * @author Stephane Nicoll
11 | */
12 | @SpringBootApplication
13 | @EnableHystrixDashboard
14 | public class HystrixDashboardApplication {
15 |
16 | public static void main(String[] args) {
17 | SpringApplication.run(HystrixDashboardApplication.class, args);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/hystrix-dashboard/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.example
6 | spring-cloud-intro-conference
7 | 0.0.1-SNAPSHOT
8 |
9 | hystrix-dashboard
10 | Spring Cloud intro :: Hystrix Dashboard
11 |
12 |
13 |
14 | org.springframework.cloud
15 | spring-cloud-starter-hystrix-dashboard
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/domain/CustomerRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.customer.domain;
17 |
18 | import org.springframework.data.repository.CrudRepository;
19 |
20 | /**
21 | * @author Oliver Gierke
22 | */
23 | public interface CustomerRepository extends CrudRepository {
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/config-server/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.example
6 | spring-cloud-intro-conference
7 | 0.0.1-SNAPSHOT
8 |
9 | config-server
10 | Spring Cloud intro :: Config Server
11 |
12 |
13 |
14 | org.springframework.cloud
15 | spring-cloud-config-server
16 |
17 |
18 |
19 |
20 |
21 |
22 | org.cloudfoundry
23 | cf-maven-plugin
24 |
25 | ${project.prefix}configserver
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/eureka-server/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.example
6 | spring-cloud-intro-conference
7 | 0.0.1-SNAPSHOT
8 |
9 | eureka-server
10 | Spring Cloud intro :: Eureka Server
11 |
12 |
13 |
14 | org.springframework.cloud
15 | spring-cloud-starter-eureka-server
16 |
17 |
18 | org.springframework.cloud
19 | spring-cloud-config-client
20 |
21 |
22 |
23 |
24 |
25 |
26 | org.cloudfoundry
27 | cf-maven-plugin
28 |
29 | ${project.prefix}eureka
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/integration/store/Store.java:
--------------------------------------------------------------------------------
1 | package example.customer.integration.store;
2 |
3 | import java.util.Map;
4 |
5 | import com.fasterxml.jackson.annotation.JsonSetter;
6 | import example.customer.domain.Location;
7 |
8 | /**
9 | * Store representation.
10 | *
11 | * @author Stephane Nicoll
12 | */
13 | public class Store {
14 |
15 | private String name;
16 |
17 | private Location location;
18 |
19 | public String getName() {
20 | return name;
21 | }
22 |
23 | public void setName(String name) {
24 | this.name = name;
25 | }
26 |
27 | public Location getLocation() {
28 | return location;
29 | }
30 |
31 | public void setLocation(Location location) {
32 | this.location = location;
33 | }
34 |
35 | @SuppressWarnings("unchecked")
36 | @JsonSetter("address")
37 | void setAddressLocation(Map content) {
38 | Map location = (Map) content.get("location");
39 | if (location != null) {
40 | setLocation(new Location((double) location.get("y"),
41 | (double) location.get("x")));
42 | }
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return "Store{" + "name='" + name + '\'' +
48 | ", location=" + location +
49 | '}';
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/store-app/src/main/java/example/store/StoreApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.store;
17 |
18 | import org.springframework.boot.SpringApplication;
19 | import org.springframework.boot.autoconfigure.SpringBootApplication;
20 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
21 |
22 | /**
23 | * Spring configuration class main application bootstrap point.
24 | *
25 | * @author Oliver Gierke
26 | * @author Stephane Nicoll
27 | */
28 | @SpringBootApplication
29 | @EnableDiscoveryClient
30 | public class StoreApplication {
31 |
32 | public static void main(String[] args) {
33 | SpringApplication.run(StoreApplication.class, args);
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/store-app/src/main/java/example/store/domain/StoreRepository.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.store.domain;
17 |
18 | import org.springframework.data.domain.Page;
19 | import org.springframework.data.domain.Pageable;
20 | import org.springframework.data.geo.Distance;
21 | import org.springframework.data.geo.Point;
22 | import org.springframework.data.repository.PagingAndSortingRepository;
23 | import org.springframework.data.repository.query.Param;
24 | import org.springframework.data.rest.core.annotation.RestResource;
25 |
26 | /**
27 | * Repository interface for out-of-the-box paginating access to {@link Store}s and a query method to find stores by
28 | * location and distance.
29 | *
30 | * @author Oliver Gierke
31 | */
32 | public interface StoreRepository extends PagingAndSortingRepository {
33 |
34 | @RestResource(rel = "by-location")
35 | Page findByAddressLocationNear(//
36 | @Param("location") Point location, @Param("distance") Distance distance, Pageable pageable);
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/customer-app/src/test/java/example/customer/domain/CustomerRepositoryIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.customer.domain;
17 |
18 | import example.customer.CustomerApplication;
19 | import org.junit.Test;
20 | import org.junit.runner.RunWith;
21 |
22 | import org.springframework.beans.factory.annotation.Autowired;
23 | import org.springframework.boot.test.SpringApplicationConfiguration;
24 | import org.springframework.test.context.ActiveProfiles;
25 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
26 |
27 | import static org.hamcrest.CoreMatchers.*;
28 | import static org.junit.Assert.*;
29 |
30 | /**
31 | * Integration tests for {@link CustomerRepository}.
32 | *
33 | * @author Oliver Gierke
34 | */
35 | @RunWith(SpringJUnit4ClassRunner.class)
36 | @SpringApplicationConfiguration(classes = CustomerApplication.class)
37 | @ActiveProfiles("test")
38 | public class CustomerRepositoryIntegrationTest {
39 |
40 | @Autowired CustomerRepository repository;
41 |
42 | @Test
43 | public void savesAndFindsCustomer() {
44 |
45 | Customer customer = new Customer("Dave", "Matthews");
46 | customer.setAddress(new Address("street", "zipCode", "city", new Location(55.349451, -131.673817)));
47 |
48 | customer = repository.save(customer);
49 |
50 | assertThat(repository.findOne(customer.getId()), is(customer));
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/web/CustomerController.java:
--------------------------------------------------------------------------------
1 | package example.customer.web;
2 |
3 | import java.util.Collection;
4 |
5 | import com.fasterxml.jackson.annotation.JsonUnwrapped;
6 | import example.customer.domain.Customer;
7 | import example.customer.domain.CustomerRepository;
8 | import example.customer.integration.store.Store;
9 | import example.customer.integration.store.StoreService;
10 |
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.hateoas.Resource;
13 | import org.springframework.web.bind.annotation.PathVariable;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 | import org.springframework.web.bind.annotation.RestController;
16 |
17 | /**
18 | * Expose additional {@link Customer}-related operations.
19 | *
20 | * @author Stephane Nicoll
21 | */
22 | @RestController
23 | public class CustomerController {
24 |
25 | private final CustomerRepository customerRepository;
26 |
27 | private final StoreService storeService;
28 |
29 | @Autowired
30 | public CustomerController(CustomerRepository customerRepository, StoreService storeService) {
31 | this.customerRepository = customerRepository;
32 | this.storeService = storeService;
33 | }
34 |
35 | @RequestMapping("/customers/{id}/profile")
36 | public Profile showProfile(@PathVariable Long id) {
37 | Customer customer = customerRepository.findOne(id);
38 | Collection> stores = storeService.fetchStoreNearbyFor(customer, "50km");
39 |
40 | return new Profile(customer, stores);
41 | }
42 |
43 |
44 | static class Profile {
45 |
46 | private final Customer customer;
47 |
48 | private final Collection> nearestStores;
49 |
50 | public Profile(Customer customer, Collection> nearestStores) {
51 | this.customer = customer;
52 | this.nearestStores = nearestStores;
53 | }
54 |
55 | @JsonUnwrapped
56 | public Customer getCustomer() {
57 | return customer;
58 | }
59 |
60 | public Collection> getNearestStores() {
61 | return nearestStores;
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/store-app/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.example
6 | spring-cloud-intro-conference
7 | 0.0.1-SNAPSHOT
8 |
9 | store-app
10 | Spring Cloud intro :: Store App
11 |
12 |
13 |
14 | org.springframework.boot
15 | spring-boot-starter-data-mongodb
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-data-rest
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-actuator
24 |
25 |
26 | org.springframework.cloud
27 | spring-cloud-config-client
28 |
29 |
30 | org.springframework.cloud
31 | spring-cloud-starter-eureka
32 |
33 |
34 |
35 | org.springframework.batch
36 | spring-batch-core
37 |
38 |
39 |
40 | de.flapdoodle.embed
41 | de.flapdoodle.embed.mongo
42 | runtime
43 |
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-test
48 | test
49 |
50 |
51 |
52 |
53 |
54 |
55 | org.cloudfoundry
56 | cf-maven-plugin
57 |
58 | ${project.prefix}store
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/domain/Location.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.customer.domain;
17 |
18 | import javax.persistence.Embeddable;
19 |
20 | /**
21 | * @author Oliver Gierke
22 | */
23 | @Embeddable
24 | public class Location {
25 |
26 | private final double latitude, longitude;
27 |
28 | public Location(double latitude, double longitude) {
29 | this.latitude = latitude;
30 | this.longitude = longitude;
31 | }
32 |
33 | protected Location() {
34 | this(0.0, 0.0);
35 | }
36 |
37 | public double getLatitude() {
38 | return latitude;
39 | }
40 |
41 | public double getLongitude() {
42 | return longitude;
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | return "Location{" + "latitude=" + latitude +
48 | ", longitude=" + longitude +
49 | '}';
50 | }
51 |
52 | @Override
53 | public boolean equals(Object o) {
54 | if (this == o) return true;
55 | if (o == null || getClass() != o.getClass()) return false;
56 |
57 | Location location = (Location) o;
58 |
59 | if (Double.compare(location.latitude, latitude) != 0) return false;
60 | return Double.compare(location.longitude, longitude) == 0;
61 |
62 | }
63 |
64 | @Override
65 | public int hashCode() {
66 | int result;
67 | long temp;
68 | temp = Double.doubleToLongBits(latitude);
69 | result = (int) (temp ^ (temp >>> 32));
70 | temp = Double.doubleToLongBits(longitude);
71 | result = 31 * result + (int) (temp ^ (temp >>> 32));
72 | return result;
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/customer-app/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.example
6 | spring-cloud-intro-conference
7 | 0.0.1-SNAPSHOT
8 |
9 | customer-app
10 | Spring Cloud intro :: Customer App
11 |
12 |
13 |
14 | org.springframework.boot
15 | spring-boot-starter-data-jpa
16 |
17 |
18 | org.springframework.boot
19 | spring-boot-starter-data-rest
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-actuator
24 |
25 |
26 | org.springframework.cloud
27 | spring-cloud-config-client
28 |
29 |
30 | org.springframework.cloud
31 | spring-cloud-starter-eureka
32 |
33 |
34 | org.springframework.cloud
35 | spring-cloud-starter-hystrix
36 |
37 |
38 |
39 | com.h2database
40 | h2
41 | runtime
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-devtools
47 | true
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-starter-test
53 | test
54 |
55 |
56 |
57 |
58 |
59 |
60 | org.cloudfoundry
61 | cf-maven-plugin
62 |
63 | ${project.prefix}customer
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/domain/Customer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.customer.domain;
17 |
18 | import javax.persistence.Entity;
19 | import javax.persistence.GeneratedValue;
20 | import javax.persistence.Id;
21 |
22 | /**
23 | * @author Oliver Gierke
24 | */
25 | @Entity
26 | public class Customer {
27 |
28 | private @Id @GeneratedValue Long id;
29 |
30 | private final String firstname, lastname;
31 |
32 | private Address address;
33 |
34 | public Customer(String firstname, String lastname) {
35 | this.firstname = firstname;
36 | this.lastname = lastname;
37 | }
38 |
39 | Customer() {
40 | this.firstname = null;
41 | this.lastname = null;
42 | }
43 |
44 | public Long getId() {
45 | return id;
46 | }
47 |
48 | public String getFirstname() {
49 | return firstname;
50 | }
51 |
52 | public String getLastname() {
53 | return lastname;
54 | }
55 |
56 | public Address getAddress() {
57 | return address;
58 | }
59 |
60 | public void setAddress(Address address) {
61 | this.address = address;
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return "Customer{" + "id=" + id +
67 | ", firstname='" + firstname + '\'' +
68 | ", lastname='" + lastname + '\'' +
69 | ", address=" + address +
70 | '}';
71 | }
72 |
73 | @Override
74 | public boolean equals(Object o) {
75 | if (this == o) return true;
76 | if (o == null || getClass() != o.getClass()) return false;
77 |
78 | Customer customer = (Customer) o;
79 |
80 | return id != null ? id.equals(customer.id) : customer.id == null;
81 |
82 | }
83 |
84 | @Override
85 | public int hashCode() {
86 | return id != null ? id.hashCode() : 0;
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/integration/store/StoreService.java:
--------------------------------------------------------------------------------
1 | package example.customer.integration.store;
2 |
3 | import java.util.Collection;
4 | import java.util.Collections;
5 |
6 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
7 | import example.customer.domain.Customer;
8 | import example.customer.domain.Location;
9 |
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.core.ParameterizedTypeReference;
12 | import org.springframework.hateoas.MediaTypes;
13 | import org.springframework.hateoas.PagedResources;
14 | import org.springframework.hateoas.Resource;
15 | import org.springframework.http.RequestEntity;
16 | import org.springframework.http.ResponseEntity;
17 | import org.springframework.stereotype.Service;
18 | import org.springframework.web.client.RestOperations;
19 | import org.springframework.web.util.UriComponentsBuilder;
20 |
21 | /**
22 | * Proxy for the store app.
23 | *
24 | * @author Stephane Nicoll
25 | */
26 | @Service
27 | public class StoreService {
28 |
29 | private final RestOperations restOperations;
30 |
31 | @Autowired
32 | public StoreService(RestOperations restOperations) {
33 | this.restOperations = restOperations;
34 | }
35 |
36 | @HystrixCommand(fallbackMethod = "fallbackFetchStoreNearbyFor")
37 | public Collection> fetchStoreNearbyFor(Customer customer, String distance) {
38 | Location location = customer.getAddress().getLocation();
39 | String locationParam = String.format("%s,%s",
40 | location.getLatitude(), location.getLongitude());
41 |
42 |
43 | UriComponentsBuilder builder = UriComponentsBuilder
44 | .fromHttpUrl("http://store-app/stores/search/findByAddressLocationNear")
45 | .queryParam("size", 5)
46 | .queryParam("location", locationParam)
47 | .queryParam("distance", distance);
48 | RequestEntity request = RequestEntity
49 | .get(builder.build().encode().toUri())
50 | .accept(MediaTypes.HAL_JSON)
51 | .build();
52 |
53 | ResponseEntity>> result = restOperations.exchange(request,
54 | new ParameterizedTypeReference>>() {
55 | });
56 |
57 | return result.getBody().getContent();
58 | }
59 |
60 | public Collection fallbackFetchStoreNearbyFor(Customer customer, String distance) {
61 | return Collections.emptyList();
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/CustomerApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.customer;
17 |
18 | import javax.annotation.PostConstruct;
19 |
20 | import example.customer.domain.Address;
21 | import example.customer.domain.Customer;
22 | import example.customer.domain.CustomerRepository;
23 | import example.customer.domain.Location;
24 |
25 | import org.springframework.beans.factory.annotation.Autowired;
26 | import org.springframework.boot.SpringApplication;
27 | import org.springframework.boot.autoconfigure.SpringBootApplication;
28 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
29 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
30 | import org.springframework.stereotype.Service;
31 |
32 | /**
33 | * Spring Boot application bootstrap class to run a customer service and
34 | * configure integration with a remote system that exposes a REST resource
35 | * to lookup stores by location.
36 | *
37 | * @author Oliver Gierke
38 | * @author Stephane Nicoll
39 | */
40 | @SpringBootApplication
41 | @EnableDiscoveryClient
42 | @EnableCircuitBreaker
43 | public class CustomerApplication {
44 |
45 | public static void main(String[] args) {
46 | SpringApplication.run(CustomerApplication.class, args);
47 | }
48 |
49 | @Service
50 | static class DataInitializr {
51 |
52 | private final CustomerRepository customers;
53 |
54 | @Autowired
55 | public DataInitializr(CustomerRepository customers) {
56 | this.customers = customers;
57 | }
58 |
59 | @PostConstruct
60 | public void init() {
61 |
62 | Customer customer = new Customer("Oliver", "Gierke");
63 | customer.setAddress(
64 | new Address("625 Avenue of the Americas", "10011", "New York", new Location(40.740337, -73.995146)));
65 |
66 | customers.save(customer);
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/store-app/src/main/java/example/store/domain/Store.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.store.domain;
17 |
18 | import org.springframework.data.annotation.Id;
19 | import org.springframework.data.mongodb.core.mapping.Document;
20 |
21 | /**
22 | * Entity to represent a {@link Store}.
23 | *
24 | * @author Oliver Gierke
25 | */
26 | @Document
27 | public class Store {
28 |
29 | private final @Id String id;
30 |
31 | private final String name;
32 |
33 | private final Address address;
34 |
35 | public Store(String name, Address address) {
36 |
37 | this.name = name;
38 | this.address = address;
39 | this.id = null;
40 | }
41 |
42 | protected Store() {
43 |
44 | this.id = null;
45 | this.name = null;
46 | this.address = null;
47 | }
48 |
49 | public String getId() {
50 | return id;
51 | }
52 |
53 | public String getName() {
54 | return name;
55 | }
56 |
57 | public Address getAddress() {
58 | return address;
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return "Store{" + "id='" + id + '\'' +
64 | ", name='" + name + '\'' +
65 | ", address=" + address +
66 | '}';
67 | }
68 |
69 | @Override
70 | public boolean equals(Object o) {
71 | if (this == o) return true;
72 | if (o == null || getClass() != o.getClass()) return false;
73 |
74 | Store store = (Store) o;
75 |
76 | if (id != null ? !id.equals(store.id) : store.id != null) return false;
77 | if (name != null ? !name.equals(store.name) : store.name != null) return false;
78 | return address != null ? address.equals(store.address) : store.address == null;
79 |
80 | }
81 |
82 | @Override
83 | public int hashCode() {
84 | int result = id != null ? id.hashCode() : 0;
85 | result = 31 * result + (name != null ? name.hashCode() : 0);
86 | result = 31 * result + (address != null ? address.hashCode() : 0);
87 | return result;
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/store-app/src/test/java/example/store/domain/StoreRepositoryIntegrationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.store.domain;
17 |
18 | import example.store.StoreApplication;
19 | import org.junit.After;
20 | import org.junit.Before;
21 | import org.junit.Test;
22 | import org.junit.runner.RunWith;
23 |
24 | import org.springframework.beans.factory.annotation.Autowired;
25 | import org.springframework.boot.test.SpringApplicationConfiguration;
26 | import org.springframework.data.domain.Page;
27 | import org.springframework.data.domain.PageRequest;
28 | import org.springframework.data.geo.Distance;
29 | import org.springframework.data.geo.Metrics;
30 | import org.springframework.data.geo.Point;
31 | import org.springframework.test.context.ActiveProfiles;
32 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
33 |
34 | import static org.hamcrest.Matchers.*;
35 | import static org.junit.Assert.*;
36 |
37 | /**
38 | * Integration tests for {@link StoreRepository}.
39 | *
40 | * @author Oliver Gierke
41 | */
42 | @RunWith(SpringJUnit4ClassRunner.class)
43 | @SpringApplicationConfiguration(classes = StoreApplication.class)
44 | @ActiveProfiles("test")
45 | public class StoreRepositoryIntegrationTest {
46 |
47 | @Autowired StoreRepository repository;
48 |
49 | @Before
50 | @After
51 | public void setUp() {
52 | repository.deleteAll();
53 | }
54 |
55 | @Test
56 | public void findsStoresByLocation() {
57 |
58 | Point location = new Point(-73.995146, 40.740337);
59 | Store store = new Store("Foo", new Address("street", "city", "zip", location));
60 |
61 | store = repository.save(store);
62 |
63 | Page stores = repository.findByAddressLocationNear(location, new Distance(1.0, Metrics.KILOMETERS),
64 | new PageRequest(0, 10));
65 |
66 | assertThat(stores.getContent(), hasSize(1));
67 | assertThat(stores.getContent(), hasItem(store));
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/store-app/src/main/java/example/store/domain/Address.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.store.domain;
17 |
18 | import org.springframework.data.geo.Point;
19 | import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
20 |
21 | /**
22 | * Value object to represent an {@link Address}.
23 | *
24 | * @author Oliver Gierke
25 | */
26 | public class Address {
27 |
28 | private final String street, city, zip;
29 |
30 | private final @GeoSpatialIndexed Point location;
31 |
32 | public Address(String street, String city, String zip, Point location) {
33 | this.street = street;
34 | this.city = city;
35 | this.zip = zip;
36 | this.location = location;
37 | }
38 |
39 | public String getStreet() {
40 | return street;
41 | }
42 |
43 | public String getCity() {
44 | return city;
45 | }
46 |
47 | public String getZip() {
48 | return zip;
49 | }
50 |
51 | public Point getLocation() {
52 | return location;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return "Address{" + "street='" + street + '\'' +
58 | ", city='" + city + '\'' +
59 | ", zip='" + zip + '\'' +
60 | ", location=" + location +
61 | '}';
62 | }
63 |
64 | @Override
65 | public boolean equals(Object o) {
66 | if (this == o) return true;
67 | if (o == null || getClass() != o.getClass()) return false;
68 |
69 | Address address = (Address) o;
70 |
71 | if (street != null ? !street.equals(address.street) : address.street != null) return false;
72 | if (city != null ? !city.equals(address.city) : address.city != null) return false;
73 | if (zip != null ? !zip.equals(address.zip) : address.zip != null) return false;
74 | return location != null ? location.equals(address.location) : address.location == null;
75 |
76 | }
77 |
78 | @Override
79 | public int hashCode() {
80 | int result = street != null ? street.hashCode() : 0;
81 | result = 31 * result + (city != null ? city.hashCode() : 0);
82 | result = 31 * result + (zip != null ? zip.hashCode() : 0);
83 | result = 31 * result + (location != null ? location.hashCode() : 0);
84 | return result;
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/customer-app/src/main/java/example/customer/domain/Address.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.customer.domain;
17 |
18 | import javax.persistence.Embeddable;
19 |
20 | /**
21 | * @author Oliver Gierke
22 | */
23 | @Embeddable
24 | public class Address {
25 |
26 | private final String street, zipCode, city;
27 |
28 | private final Location location;
29 |
30 | public Address(String street, String zipCode, String city, Location location) {
31 | this.street = street;
32 | this.zipCode = zipCode;
33 | this.city = city;
34 | this.location = location;
35 | }
36 |
37 | protected Address() {
38 | this(null, null, null, null);
39 | }
40 |
41 | public String getStreet() {
42 | return street;
43 | }
44 |
45 | public String getZipCode() {
46 | return zipCode;
47 | }
48 |
49 | public String getCity() {
50 | return city;
51 | }
52 |
53 | public Location getLocation() {
54 | return location;
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | return "Address{" + "street='" + street + '\'' +
60 | ", zipCode='" + zipCode + '\'' +
61 | ", city='" + city + '\'' +
62 | ", location=" + location +
63 | '}';
64 | }
65 |
66 | @Override
67 | public boolean equals(Object o) {
68 | if (this == o) return true;
69 | if (o == null || getClass() != o.getClass()) return false;
70 |
71 | Address address = (Address) o;
72 |
73 | if (street != null ? !street.equals(address.street) : address.street != null) return false;
74 | if (zipCode != null ? !zipCode.equals(address.zipCode) : address.zipCode != null) return false;
75 | if (city != null ? !city.equals(address.city) : address.city != null) return false;
76 | return location != null ? location.equals(address.location) : address.location == null;
77 |
78 | }
79 |
80 | @Override
81 | public int hashCode() {
82 | int result = street != null ? street.hashCode() : 0;
83 | result = 31 * result + (zipCode != null ? zipCode.hashCode() : 0);
84 | result = 31 * result + (city != null ? city.hashCode() : 0);
85 | result = 31 * result + (location != null ? location.hashCode() : 0);
86 | return result;
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | == Spring Cloud Introduction (conference format)
2 |
3 | This is the sample app used during an introduction to Spring Cloud (conference format,
4 | ~ 50min).
5 |
6 | The purpose of this sample is to break a monolith for a Coffee app and show how Spring
7 | Cloud can help in the process. Each commit represents a step towards the end goal, that
8 | is:
9 |
10 | * Create a `Profile` controller that uses both the `customer-app` and the `store-app` to
11 | provide a customer profile including their nearest stores.
12 | * Because we can't hard-code the url of the `store-app` service, we use the Spring Boot
13 | infrastructure to create a `@ConfigurationProperties` bean. This step also demonstrates
14 | the use of configuration meta-data to add assistance in the IDE.
15 | * Instead of having a configuration per app, we can share the configuration of the whole
16 | infrastructure in a single place using a config server. This step uses a separate repository
17 | for the configuration (see https://github.com/snicoll-demos/spring-cloud-intro-conference-config[spring-cloud-intro-conference-config]).
18 | * We can further improve the solution by adding a service registry (`Eureka`) so that the
19 | apps register themselves and can easily be located. This removes the use of a key for the
20 | url of the `store-app` service.
21 | * What happens if the `store-app` is down? If we want to avoid cascading failures, we can
22 | use Hystrix and the circuit breaker.
23 | * Finally, what does it take to deploy this infrastructure to the Cloud? Not much really:
24 | the only thing required is the location of the config server.
25 |
26 | === Running the app locally
27 |
28 | There are many ways to start these apps locally:
29 |
30 | * Run the `main` method (e.g. `CustomerApplication`) right from your ide
31 | * On the command-line, execute `mvn spring-boot:run`
32 | * Package the project (`mvn package`) and _just_ execute the jar, e.g.
33 | `java -jar target/customer-app-0.0.1-SNAPSHOT.jar`
34 |
35 | === Running the app in the cloud
36 |
37 | If you want to run that exact same app in the cloud, you need first an account with a
38 | cloud provider. At the time of writing, you can try _Pivotal Web Services_ for free for
39 | 60 days, check `https://run.pivotal.io/`
40 |
41 | The maven configuration expects to deploy the apps to PCF, check the root `pom.xml` for
42 | more details. You need to provide your CloudFoundry credentials to Maven. To do so,
43 | update your `~/.m2/settings.xml`
44 |
45 | ```
46 |
47 |
48 | pcf-instance
49 | your-cf-email@example.com
50 | secret
51 |
52 |
53 | ```
54 |
55 | TIP: You can avoid storing your password in plain text in your `settings.xml` as Maven
56 | https://maven.apache.org/guides/mini/guide-encryption.html[supports encryption]
57 |
58 | In order to avoid clashes with other apps, the project defines a `project-prefix`
59 | (currently set to `my-test-` in the root `pom.xml`). Make sure to update that to a unique
60 | value first!
61 |
62 | Pushing your app to the cloud should be as easy as invoking `mvn cf:push` on the command-line.
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.example
6 | spring-cloud-intro-conference
7 | 0.0.1-SNAPSHOT
8 | pom
9 | Spring Cloud intro
10 | Spring Cloud introduction sample app
11 |
12 |
13 | UTF-8
14 | 1.8
15 |
16 | 1.3.1.RELEASE
17 | Brixton.M4
18 |
19 | my-test-
20 | http://${project.prefix}configserver.cfapps.io
21 |
22 |
23 |
24 | customer-app
25 | store-app
26 |
27 | config-server
28 | eureka-server
29 | hystrix-dashboard
30 |
31 |
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-dependencies
37 | ${spring.boot.version}
38 | pom
39 | import
40 |
41 |
42 | org.springframework.cloud
43 | spring-cloud-starter-parent
44 | ${spring.cloud.version}
45 | pom
46 | import
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-maven-plugin
56 |
57 |
58 |
59 | repackage
60 |
61 |
62 |
63 |
64 |
65 | org.cloudfoundry
66 | cf-maven-plugin
67 | false
68 |
69 | true
70 |
71 |
72 |
73 |
74 |
75 |
76 | org.apache.maven.plugins
77 | maven-compiler-plugin
78 | 3.3
79 |
80 | ${java.version}
81 | ${java.version}
82 |
83 |
84 |
85 | org.apache.maven.plugins
86 | maven-resources-plugin
87 | 2.7
88 |
89 |
90 | org.cloudfoundry
91 | cf-maven-plugin
92 | 1.1.3
93 |
94 | pcf-instance
95 | http://api.run.pivotal.io
96 | spring.io
97 | development
98 |
99 | ${project.prefix}
100 | ${configserver.uri}
101 |
102 |
103 |
104 |
105 | org.springframework.boot
106 | spring-boot-maven-plugin
107 | ${spring.boot.version}
108 |
109 |
110 |
111 | repackage
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | spring-milestones
123 | Spring Milestones
124 | http://repo.spring.io/milestone
125 |
126 | false
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/store-app/src/main/java/example/store/StoreInitializer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2015 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 | * http://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 example.store;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 | import java.util.Scanner;
21 |
22 | import example.store.domain.Address;
23 | import example.store.domain.Store;
24 | import example.store.domain.StoreRepository;
25 | import org.slf4j.Logger;
26 | import org.slf4j.LoggerFactory;
27 |
28 | import org.springframework.batch.item.ExecutionContext;
29 | import org.springframework.batch.item.file.FlatFileItemReader;
30 | import org.springframework.batch.item.file.mapping.DefaultLineMapper;
31 | import org.springframework.batch.item.file.mapping.FieldSetMapper;
32 | import org.springframework.batch.item.file.separator.DefaultRecordSeparatorPolicy;
33 | import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
34 | import org.springframework.batch.item.file.transform.FieldSet;
35 | import org.springframework.beans.factory.annotation.Autowired;
36 | import org.springframework.core.io.ClassPathResource;
37 | import org.springframework.data.geo.Point;
38 | import org.springframework.data.mongodb.core.MongoOperations;
39 | import org.springframework.stereotype.Component;
40 | import org.springframework.validation.BindException;
41 |
42 | /**
43 | * Component initializing a hand full of Starbucks stores and persisting them through a {@link StoreRepository}.
44 | *
45 | * @author Oliver Gierke
46 | */
47 | @Component
48 | public class StoreInitializer {
49 |
50 | private static final Logger logger = LoggerFactory.getLogger(StoreInitializer.class);
51 |
52 | @Autowired
53 | public StoreInitializer(StoreRepository repository, MongoOperations operations) throws Exception {
54 |
55 | if (repository.count() != 0) {
56 | return;
57 | }
58 |
59 | List stores = readStores();
60 | logger.info("Importing {} stores into MongoDB…", stores.size());
61 | repository.save(stores);
62 | logger.info("Successfully imported {} stores.", repository.count());
63 | }
64 |
65 | /**
66 | * Reads a file {@code starbucks.csv} from the class path and parses it into {@link Store} instances about to
67 | * persisted.
68 | *
69 | * @return
70 | * @throws Exception
71 | */
72 | public static List readStores() throws Exception {
73 |
74 | ClassPathResource resource = new ClassPathResource("starbucks.csv");
75 | Scanner scanner = new Scanner(resource.getInputStream());
76 | String line = scanner.nextLine();
77 | scanner.close();
78 |
79 | FlatFileItemReader itemReader = new FlatFileItemReader();
80 | itemReader.setResource(resource);
81 |
82 | // DelimitedLineTokenizer defaults to comma as its delimiter
83 | DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
84 | tokenizer.setNames(line.split(","));
85 | tokenizer.setStrict(false);
86 |
87 | DefaultLineMapper lineMapper = new DefaultLineMapper();
88 | lineMapper.setLineTokenizer(tokenizer);
89 | lineMapper.setFieldSetMapper(StoreFieldSetMapper.INSTANCE);
90 | itemReader.setLineMapper(lineMapper);
91 | itemReader.setRecordSeparatorPolicy(new DefaultRecordSeparatorPolicy());
92 | itemReader.setLinesToSkip(1);
93 | itemReader.open(new ExecutionContext());
94 |
95 | List stores = new ArrayList<>();
96 | Store store = null;
97 |
98 | do {
99 |
100 | store = itemReader.read();
101 |
102 | if (store != null) {
103 | stores.add(store);
104 | }
105 |
106 | } while (store != null);
107 |
108 | return stores;
109 | }
110 |
111 | private enum StoreFieldSetMapper implements FieldSetMapper {
112 |
113 | INSTANCE;
114 |
115 | /*
116 | * (non-Javadoc)
117 | * @see org.springframework.batch.item.file.mapping.FieldSetMapper#mapFieldSet(org.springframework.batch.item.file.transform.FieldSet)
118 | */
119 | @Override
120 | public Store mapFieldSet(FieldSet fields) throws BindException {
121 |
122 | Point location = new Point(fields.readDouble("Longitude"), fields.readDouble("Latitude"));
123 | Address address = new Address(fields.readString("Street Address"), fields.readString("City"),
124 | fields.readString("Zip"), location);
125 |
126 | return new Store(fields.readString("Name"), address);
127 | }
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------