diff --git a/README.md b/README.md index f62c747e..32680a17 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ We have separate folders for the samples of individual modules: * `multi-store` - A sample REST web-service based on both Spring Data JPA and Spring Data MongoDB. * `projections` - A sample REST web-service showing how to use projections. * `security` - A sample REST web-service secured using Spring Security. +* `headeres` - A sample showing the population of HTTP headers and the usage of them to perform conditional `GET` requests. ## Spring Data Redis diff --git a/rest/headers/pom.xml b/rest/headers/pom.xml new file mode 100644 index 00000000..3f64a84e --- /dev/null +++ b/rest/headers/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + spring-data-rest-headers + + Spring Data REST - Headers Example + + + org.springframework.data.examples + spring-data-rest-examples + 1.0.0.BUILD-SNAPSHOT + + + + Gosling-BUILD-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + org.hsqldb + hsqldb + + + + + \ No newline at end of file diff --git a/rest/headers/src/main/java/example/orders/Address.java b/rest/headers/src/main/java/example/orders/Address.java new file mode 100644 index 00000000..7b2d4e8a --- /dev/null +++ b/rest/headers/src/main/java/example/orders/Address.java @@ -0,0 +1,52 @@ +/* + * 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.orders; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +/** + * @author Oliver Gierke + */ +@Entity +@Data +@RequiredArgsConstructor +public class Address { + + @GeneratedValue @Id// + private Long id; + private final String street, zipCode, city, state; + + Address() { + + this.street = null; + this.zipCode = null; + this.city = null; + this.state = null; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return String.format("%s, %s %s, %s", street, zipCode, city, state); + } +} diff --git a/rest/headers/src/main/java/example/orders/Application.java b/rest/headers/src/main/java/example/orders/Application.java new file mode 100644 index 00000000..36e2f670 --- /dev/null +++ b/rest/headers/src/main/java/example/orders/Application.java @@ -0,0 +1,46 @@ +/* + * 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.orders; + +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.data.jpa.repository.config.EnableJpaAuditing; + +import example.orders.Customer.Gender; + +/** + * @author Oliver Gierke + * @soundtrack The Intersphere - Out of phase (Live at Alte Feuerwache Mannheim) + */ +@EnableJpaAuditing +@SpringBootApplication +public class Application { + + public static void main(String... args) { + SpringApplication.run(Application.class, args); + } + + @Autowired CustomerRepository customers; + + @PostConstruct + public void init() { + customers.save(new Customer("Dave", "Matthews", Gender.MALE, // + new Address("4711 Some Place", "54321", "Charlottesville", "VA"))); + } +} diff --git a/rest/headers/src/main/java/example/orders/Customer.java b/rest/headers/src/main/java/example/orders/Customer.java new file mode 100644 index 00000000..4d4a3e31 --- /dev/null +++ b/rest/headers/src/main/java/example/orders/Customer.java @@ -0,0 +1,68 @@ +/* + * 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.orders; + +import java.time.LocalDateTime; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.Version; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * Aggregate root representing a customer. + * + * @author Oliver Gierke + * @soundtrack The Intersphere - Out of phase (Live at Alte Feuerwache Mannheim) + */ +@Entity +@Data +@RequiredArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class Customer { + + private @GeneratedValue @Id Long id; + private @Version Long version; + private @JsonIgnore @LastModifiedDate LocalDateTime lastModifiedDate; + + private final String firstname, lastname; + private final Gender gender; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)// + private final Address address; + + Customer() { + this.firstname = null; + this.lastname = null; + this.address = null; + this.gender = null; + } + + static enum Gender { + MALE, FEMALE; + } +} diff --git a/rest/headers/src/main/java/example/orders/CustomerRepository.java b/rest/headers/src/main/java/example/orders/CustomerRepository.java new file mode 100644 index 00000000..f4677d93 --- /dev/null +++ b/rest/headers/src/main/java/example/orders/CustomerRepository.java @@ -0,0 +1,26 @@ +/* + * 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.orders; + +import org.springframework.data.repository.CrudRepository; + +/** + * Spring Data repository interface to manage {@link Customer} instances. + * + * @author Oliver Gierke + * @soundtrack The Intersphere - Out of phase (Live at Alte Feuerwache Mannheim) + */ +public interface CustomerRepository extends CrudRepository {} diff --git a/rest/headers/src/test/java/example/orders/ApplicationIntegrationTests.java b/rest/headers/src/test/java/example/orders/ApplicationIntegrationTests.java new file mode 100644 index 00000000..fc1d4e57 --- /dev/null +++ b/rest/headers/src/test/java/example/orders/ApplicationIntegrationTests.java @@ -0,0 +1,47 @@ +/* + * 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.orders; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +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.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration tests to bootstrap the application. + * + * @author Oliver Gierke + * @soundtrack The Intersphere - Out of phase (Live at Alte Feuerwache Mannheim) + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = Application.class) +public class ApplicationIntegrationTests { + + @Autowired CustomerRepository repository; + + @Test + public void initializesRepositoryWithSampleData() { + + Iterable result = repository.findAll(); + + assertThat(result, is(iterableWithSize(1))); + assertThat(result.iterator().next().getLastModifiedDate(), is(notNullValue())); + } +} diff --git a/rest/headers/src/test/java/example/orders/WebIntegrationTests.java b/rest/headers/src/test/java/example/orders/WebIntegrationTests.java new file mode 100644 index 00000000..157dfe5b --- /dev/null +++ b/rest/headers/src/test/java/example/orders/WebIntegrationTests.java @@ -0,0 +1,77 @@ +/* + * 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.orders; + +import static org.springframework.http.HttpHeaders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.net.URI; + +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.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.util.UriTemplate; + +/** + * @author Oliver Gierke + * @soundtrack The Intersphere - Out of phase (Live at Alte Feuerwache Mannheim) + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@SpringApplicationConfiguration(classes = Application.class) +public class WebIntegrationTests { + + @Autowired WebApplicationContext context; + @Autowired CustomerRepository customers; + + MockMvc mvc; + + @Before + public void setUp() { + this.mvc = MockMvcBuilders.webAppContextSetup(context).build(); + } + + @Test + public void executeConditionalGetRequests() throws Exception { + + Customer customer = customers.findAll().iterator().next(); + URI uri = new UriTemplate("/customers/{id}").expand(customer.getId()); + + MockHttpServletResponse response = mvc.perform(get(uri)).// + andDo(print()).// + andReturn().getResponse(); + + // ETag-based + + mvc.perform(get(uri).header(IF_NONE_MATCH, response.getHeader(ETAG))).// + andExpect(status().isNotModified()); + + // Last-modified-based + + mvc.perform(get(uri).header(IF_MODIFIED_SINCE, response.getHeader(LAST_MODIFIED))).// + andExpect(status().isNotModified()); + } +} diff --git a/rest/pom.xml b/rest/pom.xml index ac7c856f..b3a65efa 100644 --- a/rest/pom.xml +++ b/rest/pom.xml @@ -19,6 +19,7 @@ multi-store projections security + headers