diff --git a/rest/starbucks/README.md b/rest/starbucks/README.md
index 12f5c011..e1a601d8 100644
--- a/rest/starbucks/README.md
+++ b/rest/starbucks/README.md
@@ -7,15 +7,19 @@ This sample app exposes 10843 Starbucks coffee shops via a RESTful API that allo
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`)
+4. Or access the location search directly (e.g. `http://localhost:8080/stores/search/findByAddressLocationNear?location=40.740337,-73.995146&distance=0.5miles`)
-## API exploration
+## Web UI
-The module uses the HAL Browser module of Spring Data REST which serves a UI to explore the resources exposed. Point your browser to `http://localhost:8080` to see it.
+The application provides a custom web UI using the exposed REST API to display the search result on a Google Map. Point you browser to `http://localhost:8080`. The UI is rendered using Thymeleaf, driven by the `StoresController`. A tiny JavaScript progressively enhances the view by picking up and enhancing a URI template rendered into the view (`
`).
+
+
+
+The API itself can be discovered using the HAL browser pulled in through the corresponding Spring Data REST module (`spring-data-rest-hal-browser`). It's exposed at the API root at `http://localhost:8080/api`.
## 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
\ No newline at end of file
+- Spring Boot
diff --git a/rest/starbucks/pom.xml b/rest/starbucks/pom.xml
index 90c637e5..7d315df5 100644
--- a/rest/starbucks/pom.xml
+++ b/rest/starbucks/pom.xml
@@ -40,6 +40,35 @@
runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+ runtime
+
+
+
+ org.webjars
+ jquery
+ 2.1.3
+ runtime
+
+
+
+ org.webjars
+ bootstrap
+ 3.3.4
+ runtime
+
+
+
+ org.webjars
+ URI.js
+ 1.14.1
+ runtime
+
+
@@ -54,4 +83,4 @@
-
\ No newline at end of file
+
diff --git a/rest/starbucks/src/main/java/example/stores/Address.java b/rest/starbucks/src/main/java/example/stores/Address.java
index 4ced9fac..418aa637 100644
--- a/rest/starbucks/src/main/java/example/stores/Address.java
+++ b/rest/starbucks/src/main/java/example/stores/Address.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2015 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.
@@ -30,4 +30,12 @@ public class Address {
private final String street, city, zip;
private final @GeoSpatialIndexed Point location;
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return String.format("%s, %s %s", street, zip, city);
+ }
}
diff --git a/rest/starbucks/src/main/java/example/stores/web/StoresController.java b/rest/starbucks/src/main/java/example/stores/web/StoresController.java
new file mode 100644
index 00000000..9e8d5f4d
--- /dev/null
+++ b/rest/starbucks/src/main/java/example/stores/web/StoresController.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2014-2015 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.web;
+
+import static org.springframework.data.geo.Metrics.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import lombok.RequiredArgsConstructor;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.geo.Distance;
+import org.springframework.data.geo.Metrics;
+import org.springframework.data.geo.Point;
+import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import example.stores.Store;
+import example.stores.StoreRepository;
+
+/**
+ * A Spring MVC controller to produce an HTML frontend.
+ *
+ * @author Oliver Gierke
+ */
+@Controller
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+class StoresController {
+
+ private static final List DISTANCES = Arrays.asList(new Distance(0.5, MILES), new Distance(1, MILES),
+ new Distance(2, MILES));
+ private static final Distance DEFAULT_DISTANCE = new Distance(1, Metrics.MILES);
+ private static final Map KNOWN_LOCATIONS;
+
+ static {
+
+ Map locations = new HashMap<>();
+
+ locations.put("Pivotal SF", new Point(-122.4041764, 37.7819286));
+ locations.put("Timesquare NY", new Point(-73.995146, 40.740337));
+
+ KNOWN_LOCATIONS = Collections.unmodifiableMap(locations);
+ }
+
+ private final StoreRepository repository;
+ private final RepositoryEntityLinks entityLinks;
+
+ /**
+ * Looks up the stores in the given distance around the given location.
+ *
+ * @param model the {@link Model} to populate.
+ * @param location the optional location, if none is given, no search results will be returned.
+ * @param distance the distance to use, if none is given the {@link #DEFAULT_DISTANCE} is used.
+ * @param pageable the pagination information
+ * @return
+ */
+ @RequestMapping(value = "/", method = RequestMethod.GET)
+ String index(Model model, @RequestParam Optional location, @RequestParam Optional distance,
+ Pageable pageable) {
+
+ Point point = location.orElse(KNOWN_LOCATIONS.get("Timesquare NY"));
+
+ Page stores = repository.findByAddressLocationNear(point, distance.orElse(DEFAULT_DISTANCE), pageable);
+
+ model.addAttribute("stores", stores);
+ model.addAttribute("distances", DISTANCES);
+ model.addAttribute("selectedDistance", distance.orElse(DEFAULT_DISTANCE));
+ model.addAttribute("location", point);
+ model.addAttribute("locations", KNOWN_LOCATIONS);
+ model.addAttribute("api", entityLinks.linkToSearchResource(Store.class, "by-location", pageable).getHref());
+
+ return "index";
+ }
+}
diff --git a/rest/starbucks/src/main/resources/application.properties b/rest/starbucks/src/main/resources/application.properties
new file mode 100644
index 00000000..6846da4e
--- /dev/null
+++ b/rest/starbucks/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.data.rest.base-path=/api
diff --git a/rest/starbucks/src/main/resources/static/img/map.png b/rest/starbucks/src/main/resources/static/img/map.png
new file mode 100644
index 00000000..05d2959e
Binary files /dev/null and b/rest/starbucks/src/main/resources/static/img/map.png differ
diff --git a/rest/starbucks/src/main/resources/templates/index.html b/rest/starbucks/src/main/resources/templates/index.html
new file mode 100644
index 00000000..a4e7c88c
--- /dev/null
+++ b/rest/starbucks/src/main/resources/templates/index.html
@@ -0,0 +1,197 @@
+
+
+
+ Starbucks Storefinder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Starbucks Storefinder
+
+
+
+
Search
+
+
+
+
+
+
+
+
+
+
+
Results
+
+
+
+
+
+
+
+
+
Found 1 results.
+
+
Store name
+
+
+
No Results
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rest/starbucks/webui.png b/rest/starbucks/webui.png
new file mode 100644
index 00000000..ad667299
Binary files /dev/null and b/rest/starbucks/webui.png differ