Added Starbucks sample for Spring Data REST.

This commit is contained in:
Oliver Gierke
2014-05-02 20:06:43 +02:00
parent d5138bb3a4
commit 7a7daaec60
11 changed files with 32956 additions and 0 deletions

View File

@@ -18,6 +18,7 @@
<modules>
<module>jpa</module>
<module>mongodb</module>
<module>rest</module>
</modules>
<properties>

21
rest/pom.xml Normal file
View File

@@ -0,0 +1,21 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-rest-examples</artifactId>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<name>Spring Data REST - Examples</name>
<description>Sample projects for Spring Data REST</description>
<modules>
<module>starbucks</module>
</modules>
</project>

17
rest/starbucks/README.md Normal file
View File

@@ -0,0 +1,17 @@
# Spring Data REST - Starbucks example
This sample app exposes 10843 Starbucks coffee shops via a RESTful API that allows to access the stores in a hypermedia based way and exposes a resource to execute geo-location search for coffee shops.
## Quickstart
1. Install MongoDB (http://www.mongodb.org/downloads, unzip, run `bin/mongod --dbpath=data`)
2. Build and run the app (`mvn spring-boot:run`)
3. Access the root resource (`curl http://localhost:8080`) and traverse hyperlinks.
4. Or access the location search directly (e.g. `localhost:8080/stores/search/findByAddressLocationNear?location=40.740337,-73.995146&distance=0.5miles`)
## Technologies used
- Spring Data REST & Spring Data MongoDB
- MongoDB
- Spring Batch (to read the CSV file containing the store data and pipe it into MongoDB)
- Spring Boot

41
rest/starbucks/pom.xml Normal file
View File

@@ -0,0 +1,41 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-rest-starbucks</artifactId>
<name>Spring Data REST - Starbucks Example</name>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-rest-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</parent>
<properties>
<mongodb.version>2.12.1</mongodb.version>
<spring-data-mongodb.version>1.5.0.RC1</spring-data-mongodb.version>
<spring-data-rest.version>2.1.0.RC1</spring-data-rest.version>
<spring-hateoas.version>0.11.0.RELEASE</spring-hateoas.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.stores;
import lombok.Value;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
/**
* Value object to represent an {@link Address}.
*
* @author Oliver Gierke
*/
@Value
public class Address {
private final String street, city, zip;
private final @GeoSpatialIndexed Point location;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.stores;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* Entity to represent a {@link Store}.
*
* @author Oliver Gierke
*/
@Data
@Document
public class Store {
private final @Id String id;
private final String name;
private final Address address;
public Store(String name, Address address) {
this.name = name;
this.address = address;
this.id = null;
}
protected Store() {
this.id = null;
this.name = null;
this.address = null;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.stores;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
/**
* Spring configuration class main application bootstrap point.
*
* @author Oliver Gierke
*/
@EnableAutoConfiguration
@ComponentScan
// Activates Spring Data REST
@Import(RepositoryRestMvcConfiguration.class)
public class StoreApp {
public static void main(String[] args) {
SpringApplication.run(StoreApp.class, args);
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.stores;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.separator.DefaultRecordSeparatorPolicy;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
/**
* Component initializing a hand full of Starbucks stores and persisting them through a {@link StoreRepository}.
*
* @author Oliver Gierke
*/
@Slf4j
@Component
public class StoreInitializer {
@Autowired
public StoreInitializer(StoreRepository repository, MongoOperations operations) throws Exception {
if (repository.count() != 0) {
return;
}
List<Store> stores = readStores();
log.info("Importing {} stores into MongoDB…", stores.size());
repository.save(stores);
log.info("Successfully imported {} stores.", repository.count());
}
/**
* Reads a file {@code starbucks.csv} from the class path and parses it into {@link Store} instances about to
* persisted.
*
* @return
* @throws Exception
*/
public static List<Store> readStores() throws Exception {
ClassPathResource resource = new ClassPathResource("starbucks.csv");
Scanner scanner = new Scanner(resource.getInputStream());
String line = scanner.nextLine();
scanner.close();
FlatFileItemReader<Store> itemReader = new FlatFileItemReader<Store>();
itemReader.setResource(resource);
// DelimitedLineTokenizer defaults to comma as its delimiter
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setNames(line.split(","));
tokenizer.setStrict(false);
DefaultLineMapper<Store> lineMapper = new DefaultLineMapper<Store>();
lineMapper.setLineTokenizer(tokenizer);
lineMapper.setFieldSetMapper(StoreFieldSetMapper.INSTANCE);
itemReader.setLineMapper(lineMapper);
itemReader.setRecordSeparatorPolicy(new DefaultRecordSeparatorPolicy());
itemReader.setLinesToSkip(1);
itemReader.open(new ExecutionContext());
List<Store> stores = new ArrayList<>();
Store store = null;
do {
store = itemReader.read();
if (store != null) {
stores.add(store);
}
} while (store != null);
return stores;
}
private static enum StoreFieldSetMapper implements FieldSetMapper<Store> {
INSTANCE;
/*
* (non-Javadoc)
* @see org.springframework.batch.item.file.mapping.FieldSetMapper#mapFieldSet(org.springframework.batch.item.file.transform.FieldSet)
*/
@Override
public Store mapFieldSet(FieldSet fields) throws BindException {
Point location = new Point(fields.readDouble("Longitude"), fields.readDouble("Latitude"));
Address address = new Address(fields.readString("Street Address"), fields.readString("City"),
fields.readString("Zip"), location);
return new Store(fields.readString("Name"), address);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.stores;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RestResource;
/**
* Repository interface for out-of-the-box paginating access to {@link Store}s and a query method to find stores by
* location and distance.
*
* @author Oliver Gierke
*/
public interface StoreRepository extends PagingAndSortingRepository<Store, String> {
@RestResource(rel = "by-location")
Page<Store> findByAddressLocationNear(//
@Param("location") Point location, @Param("distance") Distance distance, Pageable pageable);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.stores;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for {@link StoreRepository}.
*
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = StoreApp.class)
public class StoreRepositoryIntegrationTests {
@Autowired StoreRepository repository;
@Before
@After
public void clearDb() {
repository.deleteAll();
}
@Test
public void findsStoresByLocation() {
Point location = new Point(-73.995146, 40.740337);
Store store = new Store("Foo", new Address("street", "city", "zip", location));
store = repository.save(store);
Page<Store> stores = repository.findByAddressLocationNear(location, new Distance(1.0, Metrics.KILOMETERS),
new PageRequest(0, 10));
assertThat(stores.getContent(), hasSize(1));
assertThat(stores.getContent(), hasItem(store));
}
}