diff --git a/README.md b/README.md
index 73d94d03..59f305fd 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,10 @@ We have separate folders for the samples of individual modules:
* `example` - Example how to use basic text search, geo-spatial search and facets.
+## Spring Data web support
+
+* `web` - Example for Spring Data web integration (binding `Pageable` instances to Spring MVC controller methods, using interfaces to bind Spring MVCrequest payloads).
+
## Miscellaneous
* `multi-store` - Example project to use both Spring Data MongoDB and Spring Data JPA in one project.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index b694e03e..b7b13e3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,7 @@
solrcassandraelasticsearch
+ web
diff --git a/web/README.md b/web/README.md
new file mode 100644
index 00000000..3940d0ea
--- /dev/null
+++ b/web/README.md
@@ -0,0 +1,6 @@
+# Spring Data - web support example
+
+This example shows some of the Spring Data integration features with Spring MVC.
+
+1. See how we plug into Spring MVC to create `Pageable` instances from request parameters in `UserController.users(…)`.
+2. See how interfaces can be used to bind request payloads in `UserController.UserForm`. Spring Data creates a `Map`-backed proxy for you to easily create form-backing objects.
\ No newline at end of file
diff --git a/web/pom.xml b/web/pom.xml
new file mode 100644
index 00000000..5fa53fe7
--- /dev/null
+++ b/web/pom.xml
@@ -0,0 +1,48 @@
+
+ 4.0.0
+
+ spring-data-web-example
+
+ Spring Data - Web support example
+
+
+ org.springframework.data.examples
+ spring-data-examples
+ 1.0.0.BUILD-SNAPSHOT
+
+
+
+ Fowler-BUILD-SNAPSHOT
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+ org.hsqldb
+ hsqldb
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/main/java/example/Application.java b/web/src/main/java/example/Application.java
new file mode 100644
index 00000000..7d3443b4
--- /dev/null
+++ b/web/src/main/java/example/Application.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 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;
+
+import java.util.stream.IntStream;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import example.users.Password;
+import example.users.UserManagement;
+import example.users.Username;
+
+/**
+ * Central Spring Boot application class to bootstrap the application. Excludes Spring Security auto-configuration as we
+ * don't need it for the example but only want to use a {@link PasswordEncoder} (see {@link #passwordEncoder()}).
+ *
+ * Spring Data web support is transparently activated by Boot for you. In case you want to manually activate it, use
+ * {@link EnableSpringDataWebSupport}. The core aspects of the enabled functionality shown in this example are:
+ *
+ *
Automatic population of a {@link Pageable} instances from request parameters (see
+ * {@link example.users.web.UserController#users(Pageable)})
+ *
The ability to use proxy-backed interfaces to bind request payloads (see
+ * {@link example.users.web.UserController.UserForm})
+ *
+ *
+ * @author Oliver Gierke
+ */
+@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
+public class Application {
+
+ public static void main(String... args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+ @Autowired UserManagement userManagement;
+
+ /**
+ * Creates a few sample users.
+ */
+ @PostConstruct
+ public void init() {
+
+ IntStream.range(0, 41).forEach(index -> {
+ userManagement.register(new Username("user" + index), Password.raw("foobar"));
+ });
+ }
+
+ /**
+ * A Spring Security {@link PasswordEncoder} to encrypt passwords for newly created users, used in
+ * {@link UserManagement}.
+ *
+ * @return
+ */
+ public @Bean PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
diff --git a/web/src/main/java/example/users/Password.java b/web/src/main/java/example/users/Password.java
new file mode 100644
index 00000000..f2fb6ead
--- /dev/null
+++ b/web/src/main/java/example/users/Password.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 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.users;
+
+import javax.persistence.Embeddable;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.experimental.Delegate;
+
+/**
+ * A value object to represent {@link Password}s in encrypted and unencrypted state. Note how the methods to create a
+ * {@link Password} in encrypted state are restricted to package scope so that only the user subsystem is actually able
+ * to encrypted passwords.
+ *
+ * @author Oliver Gierke
+ */
+@EqualsAndHashCode
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter(AccessLevel.PACKAGE)
+@Embeddable
+public class Password implements CharSequence {
+
+ private @Delegate final String password;
+ private @Getter transient boolean encrypted;
+
+ Password() {
+ this.password = null;
+ this.encrypted = true;
+ }
+
+ /**
+ * Creates a new raw {@link Password} for the given source {@link String}.
+ *
+ * @param password must not be {@literal null} or empty.
+ * @return
+ */
+ public static Password raw(String password) {
+ return new Password(password, false);
+ }
+
+ /**
+ * Creates a new encrypted {@link Password} for the given {@link String}. Note how this method is package protected so
+ * that encrypted passwords can only created by components in this package and not accidentally by clients using the
+ * type from other packages.
+ *
+ * @param password must not be {@literal null} or empty.
+ * @return
+ */
+ static Password encrypted(String password) {
+ return new Password(password, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return encrypted ? password : "********";
+ }
+}
diff --git a/web/src/main/java/example/users/User.java b/web/src/main/java/example/users/User.java
new file mode 100644
index 00000000..37a251a1
--- /dev/null
+++ b/web/src/main/java/example/users/User.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 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.users;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.PrePersist;
+import javax.persistence.PreUpdate;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * A {@link User} domain object. The primary entity of this example. Basically a combination of a {@link Username} and
+ * {@link Password}.
+ *
+ * @author Oliver Gierke
+ */
+@Entity
+@Getter
+@RequiredArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
+@EqualsAndHashCode(of = "id")
+public class User {
+
+ private @GeneratedValue @Id Long id;
+ private final Username username;
+ private final Password password;
+
+ User() {
+ this.username = null;
+ this.password = null;
+ }
+
+ /**
+ * Makes sure only {@link User}s with encrypted {@link Password} can be persisted.
+ */
+ @PrePersist
+ @PreUpdate
+ void assertEncrypted() {
+
+ if (!password.isEncrypted()) {
+ throw new IllegalStateException("Tried to persist/load a user with a non-encrypted password!");
+ }
+ }
+}
diff --git a/web/src/main/java/example/users/UserManagement.java b/web/src/main/java/example/users/UserManagement.java
new file mode 100644
index 00000000..523ea986
--- /dev/null
+++ b/web/src/main/java/example/users/UserManagement.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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.users;
+
+import java.util.Optional;
+
+import javax.transaction.Transactional;
+
+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.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+
+/**
+ * Domain service to register {@link User}s in the system.
+ *
+ * @author Oliver Gierke
+ */
+@Transactional
+@Service
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+public class UserManagement {
+
+ private final UserRepository repository;
+ private final PasswordEncoder encoder;
+
+ /**
+ * Registers a {@link User} with the given {@link Username} and {@link Password}.
+ *
+ * @param username must not be {@literal null}.
+ * @param password must not be {@literal null}.
+ * @return
+ */
+ public User register(Username username, Password password) {
+
+ Assert.notNull(username, "Username must not be null!");
+ Assert.notNull(password, "Password must not be null!");
+
+ repository.findByUsername(username).ifPresent(user -> {
+ throw new IllegalArgumentException("User with that name already exists!");
+ });
+
+ Password encryptedPassword = Password.encrypted(encoder.encode(password));
+
+ return repository.save(new User(username, encryptedPassword));
+ }
+
+ /**
+ * Returns a {@link Page} of {@link User} for the given {@link Pageable}.
+ *
+ * @param pageable must not be {@literal null}.
+ * @return
+ */
+ public Page findAll(Pageable pageable) {
+
+ Assert.notNull(pageable, "Pageable must not be null!");
+
+ return repository.findAll(pageable);
+ }
+
+ /**
+ * Returns the {@link User} with the given {@link Username}.
+ *
+ * @param username must not be {@literal null}.
+ * @return
+ */
+ public Optional findByUsername(Username username) {
+
+ Assert.notNull(username, "Username must not be null!");
+
+ return repository.findByUsername(username);
+ }
+}
diff --git a/web/src/main/java/example/users/UserRepository.java b/web/src/main/java/example/users/UserRepository.java
new file mode 100644
index 00000000..00ca1194
--- /dev/null
+++ b/web/src/main/java/example/users/UserRepository.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.users;
+
+import java.util.Optional;
+
+import org.springframework.data.repository.PagingAndSortingRepository;
+
+/**
+ * A Spring Data repository to persist {@link User}s.
+ *
+ * @author Oliver Gierke
+ */
+interface UserRepository extends PagingAndSortingRepository {
+
+ /**
+ * Returns the user with the given {@link Username}.
+ *
+ * @param username can be {@literal null}.
+ * @return
+ */
+ Optional findByUsername(Username username);
+}
diff --git a/web/src/main/java/example/users/Username.java b/web/src/main/java/example/users/Username.java
new file mode 100644
index 00000000..bb90f9ef
--- /dev/null
+++ b/web/src/main/java/example/users/Username.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 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.users;
+
+import javax.persistence.Embeddable;
+
+import lombok.EqualsAndHashCode;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * value object to represent user names.
+ *
+ * @author Oliver Gierke
+ */
+@EqualsAndHashCode
+@Embeddable
+public class Username {
+
+ private final String username;
+
+ Username() {
+ this.username = null;
+ }
+
+ /**
+ * Creates a new {@link Username}.
+ *
+ * @param username must not be {@literal null} or empty.
+ */
+ public Username(String username) {
+
+ if (!StringUtils.hasText(username)) {
+ throw new IllegalArgumentException("Invalid username!");
+ }
+
+ this.username = username;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return username;
+ }
+}
diff --git a/web/src/main/java/example/users/web/UserController.java b/web/src/main/java/example/users/web/UserController.java
new file mode 100644
index 00000000..14164144
--- /dev/null
+++ b/web/src/main/java/example/users/web/UserController.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 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.users.web;
+
+import static org.springframework.validation.ValidationUtils.*;
+
+import java.util.Map;
+
+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.web.PageableDefault;
+import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.view.RedirectView;
+
+import example.users.Password;
+import example.users.User;
+import example.users.UserManagement;
+import example.users.Username;
+
+/**
+ * A sample controller implementation to showcase Spring Data web support:
+ *
+ *
Automatic population of a {@link Pageable} instance as controller method argument. This is achieved by the
+ * automatic activation of {@link EnableSpringDataWebSupport} and in turn its registration of a
+ * {@link PageableHandlerMethodArgumentResolver}.
+ *
Usage of proxy-backed interfaces to bind request parameters.
+ *
+ *
+ * @author Oliver Gierke
+ */
+@Controller
+@RequiredArgsConstructor(onConstructor = @__(@Autowired))
+@RequestMapping("/users")
+class UserController {
+
+ private final UserManagement userManagement;
+
+ /**
+ * Equis the model with a {@link Page} of {@link User}s. Spring Data automatically populates the {@link Pageable} from
+ * request data according to the setup of {@link PageableHandlerMethodArgumentResolver}. Note how the defaults can be
+ * tweaked by using {@link PageableDefault}.
+ *
+ * @param pageable will never be {@literal null}.
+ * @return
+ */
+ @ModelAttribute("users")
+ public Page users(@PageableDefault(size = 5) Pageable pageable) {
+ return userManagement.findAll(pageable);
+ }
+
+ /**
+ * Registers a new {@link User} for the data provided by the given {@link UserForm}. Note, how an interface is used to
+ * bind request parameters.
+ *
+ * @param form the request data bound to the {@link UserForm} instance.
+ * @param binding the result of the binding operation.
+ * @param model the Spring MVC {@link Model}.
+ * @return
+ */
+ @RequestMapping(method = RequestMethod.POST)
+ public Object register(UserForm userForm, BindingResult binding, Model model) {
+
+ userForm.validate(binding, userManagement);
+
+ if (binding.hasErrors()) {
+ return "users";
+ }
+
+ userManagement.register(new Username(userForm.getUsername()), Password.raw(userForm.getPassword()));
+
+ RedirectView redirectView = new RedirectView("redirect:/users");
+ redirectView.setPropagateQueryParams(true);
+
+ return redirectView;
+ }
+
+ /**
+ * Populates the {@link Model} with the {@link UserForm} automatically created by Spring Data web components. It will
+ * create a {@link Map}-backed proxy for the interface.
+ *
+ * @param model will never be {@literal null}.
+ * @param userForm will never be {@literal null}.
+ * @return
+ */
+ @RequestMapping(method = RequestMethod.GET)
+ public String listUsers(Model model, UserForm userForm) {
+
+ model.addAttribute("userForm", userForm);
+
+ return "users";
+ }
+
+ /**
+ * An interface to represent the form to be used
+ *
+ * @author Oliver Gierke
+ */
+ interface UserForm {
+
+ String getUsername();
+
+ String getPassword();
+
+ String getRepeatedPassword();
+
+ /**
+ * Validates the {@link UserForm}.
+ *
+ * @param errors
+ * @param userManagement
+ */
+ default void validate(BindingResult errors, UserManagement userManagement) {
+
+ rejectIfEmptyOrWhitespace(errors, "username", "user.username.empty");
+ rejectIfEmptyOrWhitespace(errors, "password", "user.password.empty");
+ rejectIfEmptyOrWhitespace(errors, "repeatedPassword", "user.repeatedPassword.empty");
+
+ if (!getPassword().equals(getRepeatedPassword())) {
+ errors.rejectValue("repeatedPassword", "user.password.no-match");
+ }
+
+ try {
+
+ userManagement.findByUsername(new Username(getUsername())).ifPresent(
+ user -> errors.rejectValue("username", "user.username.exists"));
+
+ } catch (IllegalArgumentException o_O) {
+ errors.rejectValue("username", "user.username.invalidFormat");
+ }
+ }
+ }
+}
diff --git a/web/src/main/resources/messages.properties b/web/src/main/resources/messages.properties
new file mode 100644
index 00000000..adf14cde
--- /dev/null
+++ b/web/src/main/resources/messages.properties
@@ -0,0 +1,2 @@
+user.username.exists = Username already exists!
+user.password.no-match = The given passwords don't match!
\ No newline at end of file
diff --git a/web/src/main/resources/static/css/style.css b/web/src/main/resources/static/css/style.css
new file mode 100644
index 00000000..63df6315
--- /dev/null
+++ b/web/src/main/resources/static/css/style.css
@@ -0,0 +1,28 @@
+body {
+ margin: auto;
+ width: 50%;
+}
+
+form.user-form {
+ padding: 9px 14px;
+ border: 1px solid #e1e1e8;
+ border-radius: 4px;
+}
+
+.fieldError {
+ border: 1px solid #a94442;
+}
+
+.errors {
+ padding: 1em;
+ margin: 1em 0;
+ border: 1px solid #eee;
+ border-left-width: 5px;
+ border-left-color: #a94442;
+ border-radius: 5px;
+}
+
+.errors li {
+ list-style-type: none;
+ margin: 0.5em 0.7em;
+}
diff --git a/web/src/main/resources/templates/users.html b/web/src/main/resources/templates/users.html
new file mode 100644
index 00000000..10362245
--- /dev/null
+++ b/web/src/main/resources/templates/users.html
@@ -0,0 +1,53 @@
+
+
+
+ Users
+
+
+
+
+
+
Users
+
+
+
+
+
Username
+
+
+
+
+
\ No newline at end of file
diff --git a/web/src/test/java/example/users/AbstractIntegrationTests.java b/web/src/test/java/example/users/AbstractIntegrationTests.java
new file mode 100644
index 00000000..5912f6b3
--- /dev/null
+++ b/web/src/test/java/example/users/AbstractIntegrationTests.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.users;
+
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.SpringApplicationConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import example.Application;
+
+/**
+ * Integration tests to bootstrap the application.
+ *
+ * @author Oliver Gierke
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringApplicationConfiguration(classes = Application.class)
+public abstract class AbstractIntegrationTests {}
diff --git a/web/src/test/java/example/users/UserManagementIntegrationTests.java b/web/src/test/java/example/users/UserManagementIntegrationTests.java
new file mode 100644
index 00000000..f28386ed
--- /dev/null
+++ b/web/src/test/java/example/users/UserManagementIntegrationTests.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.users;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Integration tests for {@link UserManagement}.
+ *
+ * @author Oliver Gierke
+ */
+public class UserManagementIntegrationTests extends AbstractIntegrationTests {
+
+ @Autowired UserManagement userManagement;
+
+ /**
+ * @see #65
+ */
+ @Test
+ public void encryptsPasswordWhenCreatingAUser() {
+
+ User user = userManagement.register(new Username("olivergierke"), Password.raw("foobar"));
+
+ assertThat(user.getPassword().isEncrypted(), is(true));
+ }
+}
diff --git a/web/src/test/java/example/users/UserRepositoryIntegrationTests.java b/web/src/test/java/example/users/UserRepositoryIntegrationTests.java
new file mode 100644
index 00000000..0777e864
--- /dev/null
+++ b/web/src/test/java/example/users/UserRepositoryIntegrationTests.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.users;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+
+/**
+ * Integration tests for {@link UserRepository}.
+ *
+ * @author Oliver Gierke
+ */
+public class UserRepositoryIntegrationTests extends AbstractIntegrationTests {
+
+ @Autowired UserRepository users;
+
+ /**
+ * @see #65
+ */
+ @Test(expected = InvalidDataAccessApiUsageException.class)
+ public void repositoryRejectsUnencryptedPassword() {
+ users.save(new User(new Username("olivergierke"), Password.raw("foobar")));
+ }
+}