├── .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 | --------------------------------------------------------------------------------