From b61875f97cc2dffd0f024c59d801751d4d8853d3 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 21 Nov 2017 13:03:04 +0100 Subject: [PATCH] #265 - Polishing. Add license headers. Replace CouchbaseConfiguration with Spring Boot properties. Post-process example data to make it accessible for repository use. Fix Id type. Add examples for N1ql and view access. Enable couchbase examples in parent pom. Add test rule to skip tests if Couchbase is not available. Original pull request: #275. --- couchbase/example/README.md | 7 ++ couchbase/example/pom.xml | 33 +++-- .../couchbase/CouchbaseConfiguration.java | 35 ------ .../springdata/couchbase/model/Airline.java | 26 +++- .../repository/AirlineRepository.java | 44 ++++++- .../repository/CouchbaseConfiguration.java | 67 +++++++++++ .../src/main/resources/application.properties | 9 ++ .../AirlineRepositoryIntegrationTest.java | 84 ------------- .../AirlineRepositoryIntegrationTests.java | 113 ++++++++++++++++++ couchbase/pom.xml | 13 +- couchbase/util/pom.xml | 24 ++++ .../util/CouchbaseAvailableRule.java | 69 +++++++++++ pom.xml | 2 +- 13 files changed, 371 insertions(+), 155 deletions(-) create mode 100644 couchbase/example/README.md delete mode 100644 couchbase/example/src/main/java/example/springdata/couchbase/CouchbaseConfiguration.java create mode 100644 couchbase/example/src/main/java/example/springdata/couchbase/repository/CouchbaseConfiguration.java create mode 100644 couchbase/example/src/main/resources/application.properties delete mode 100644 couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTest.java create mode 100644 couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTests.java create mode 100644 couchbase/util/pom.xml create mode 100644 couchbase/util/src/main/java/example/springdata/couchbase/util/CouchbaseAvailableRule.java diff --git a/couchbase/example/README.md b/couchbase/example/README.md new file mode 100644 index 00000000..3b7a2f7c --- /dev/null +++ b/couchbase/example/README.md @@ -0,0 +1,7 @@ +# Spring Data Couchbase - Examples + +This project contains samples of data access features with Spring Data (Couchbase). + +## Prerequisites + +The examples require a running [Couchbase Server](https://www.couchbase.com/downloads) server with the travel sample bucket imported. We assume you're running Couchbase 5 and we have `spring.couchbase.bucket.password=…` accordingly to adapt RBAC authentication. diff --git a/couchbase/example/pom.xml b/couchbase/example/pom.xml index f384dc3e..ba38ed41 100644 --- a/couchbase/example/pom.xml +++ b/couchbase/example/pom.xml @@ -2,27 +2,26 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - spring-data-couchbase-example - Basic sample for Spring Data Couchbase - spring-data-couchbase-examples org.springframework.data.examples - 1.0.0.BUILD-SNAPSHOT + 2.0.0.BUILD-SNAPSHOT + ../pom.xml - - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*IntegrationTest* - - - - - + spring-data-couchbase-example + Basic sample for Spring Data Couchbase + Small sample project showing the usage of Spring Data Couchbase. + + + + + ${project.groupId} + spring-data-couchbase-example-utils + ${project.version} + test + + + diff --git a/couchbase/example/src/main/java/example/springdata/couchbase/CouchbaseConfiguration.java b/couchbase/example/src/main/java/example/springdata/couchbase/CouchbaseConfiguration.java deleted file mode 100644 index f8433a39..00000000 --- a/couchbase/example/src/main/java/example/springdata/couchbase/CouchbaseConfiguration.java +++ /dev/null @@ -1,35 +0,0 @@ -package example.springdata.couchbase; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; -import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; - -import java.util.Collections; -import java.util.List; - -/** - * Couchbase Configuration to connect to Couchbase data store - * - * @author Chandana Kithalagama - */ -@SpringBootApplication -@Configuration -@EnableCouchbaseRepositories -public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration { - - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("192.168.99.100"); - } - - @Override - protected String getBucketName() { - return "travel-sample"; - } - - @Override - protected String getBucketPassword() { - return ""; - } -} diff --git a/couchbase/example/src/main/java/example/springdata/couchbase/model/Airline.java b/couchbase/example/src/main/java/example/springdata/couchbase/model/Airline.java index 4a13d9e7..2408f0ea 100644 --- a/couchbase/example/src/main/java/example/springdata/couchbase/model/Airline.java +++ b/couchbase/example/src/main/java/example/springdata/couchbase/model/Airline.java @@ -1,9 +1,26 @@ +/* + * Copyright 2017 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.springdata.couchbase.model; +import lombok.Data; + +import org.springframework.data.couchbase.core.mapping.Document; + import com.couchbase.client.java.repository.annotation.Field; import com.couchbase.client.java.repository.annotation.Id; -import lombok.*; -import org.springframework.data.couchbase.core.mapping.Document; /** * A domain object representing an Airline @@ -11,13 +28,11 @@ import org.springframework.data.couchbase.core.mapping.Document; * @author Chandana Kithalagama */ @Data -@AllArgsConstructor -@NoArgsConstructor @Document public class Airline { @Id - private int id; + private String id; @Field private String type; @@ -36,5 +51,4 @@ public class Airline { @Field private String country; - } diff --git a/couchbase/example/src/main/java/example/springdata/couchbase/repository/AirlineRepository.java b/couchbase/example/src/main/java/example/springdata/couchbase/repository/AirlineRepository.java index 0a5b07e0..b64c179f 100644 --- a/couchbase/example/src/main/java/example/springdata/couchbase/repository/AirlineRepository.java +++ b/couchbase/example/src/main/java/example/springdata/couchbase/repository/AirlineRepository.java @@ -1,18 +1,52 @@ +/* + * Copyright 2017 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.springdata.couchbase.repository; import example.springdata.couchbase.model.Airline; -import org.springframework.data.couchbase.core.query.Query; -import org.springframework.data.repository.CrudRepository; import java.util.List; +import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; +import org.springframework.data.couchbase.core.query.View; +import org.springframework.data.couchbase.core.query.ViewIndexed; +import org.springframework.data.repository.CrudRepository; + /** * Repository interface to manage {@link Airline} instances. * * @author Chandana Kithalagama + * @author Mark Paluch */ -public interface AirlineRepository extends CrudRepository{ +@N1qlPrimaryIndexed +@ViewIndexed(designDoc = "airlines") +public interface AirlineRepository extends CrudRepository { - @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND name = $1") - List findAirlineByName(String name); + /** + * Derived query selecting by {@code iataCode}. + * + * @param code + * @return + */ + Airline findAirlineByIataCode(String code); + + /** + * Query method using {@code airlines/all} view. + * + * @return + */ + @View(designDocument = "airlines", viewName = "all") + List findAllBy(); } diff --git a/couchbase/example/src/main/java/example/springdata/couchbase/repository/CouchbaseConfiguration.java b/couchbase/example/src/main/java/example/springdata/couchbase/repository/CouchbaseConfiguration.java new file mode 100644 index 00000000..b464332b --- /dev/null +++ b/couchbase/example/src/main/java/example/springdata/couchbase/repository/CouchbaseConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 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.springdata.couchbase.repository; + +import example.springdata.couchbase.model.Airline; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.data.couchbase.config.BeanNames; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.repository.support.IndexManager; + +import com.couchbase.client.java.query.N1qlQuery; + +/** + * Simple configuration class. + * + * @author Chandana Kithalagama + * @author Mark Paluch + */ +@SpringBootApplication +@RequiredArgsConstructor +public class CouchbaseConfiguration { + + private final CouchbaseOperations couchbaseOperations; + + /** + * Create an {@link IndexManager} that allows index creation. + * + * @return + */ + @Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER) + public IndexManager indexManager() { + return new IndexManager(true, true, false); + } + + @PostConstruct + private void postConstruct() { + + // Need to post-process travel data to add _class attribute + List airlinesWithoutClassAttribute = couchbaseOperations.findByN1QL(N1qlQuery.simple( // + "SELECT META(`travel-sample`).id AS _ID, META(`travel-sample`).cas AS _CAS, `travel-sample`.* " + // + "FROM `travel-sample` " + // + "WHERE type = \"airline\" AND _class IS MISSING;"), + Airline.class); + + airlinesWithoutClassAttribute.forEach(couchbaseOperations::save); + } +} diff --git a/couchbase/example/src/main/resources/application.properties b/couchbase/example/src/main/resources/application.properties new file mode 100644 index 00000000..2d12d619 --- /dev/null +++ b/couchbase/example/src/main/resources/application.properties @@ -0,0 +1,9 @@ +spring.couchbase.bucket.name=travel-sample +spring.couchbase.bootstrap-hosts=localhost + +# Required for Couchbase 5 +spring.couchbase.bucket.password=password + +# Increased timeout to fit slower environments like TravisCI +spring.couchbase.env.timeouts.view=15000 +spring.couchbase.env.timeouts.query=15000 diff --git a/couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTest.java b/couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTest.java deleted file mode 100644 index 31ae448e..00000000 --- a/couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package example.springdata.couchbase.repository; - -import example.springdata.couchbase.CouchbaseConfiguration; -import example.springdata.couchbase.model.Airline; -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.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for basic CRUD operations - * - * @author Chandana Kithalagama - */ -@RunWith(SpringRunner.class) -@SpringBootTest(classes = CouchbaseConfiguration.class) -public class AirlineRepositoryIntegrationTest { - - @Autowired - AirlineRepository airlineRepository; - Airline a, b, c; - List airlineList; - - @Before - public void setup() { - a = new Airline(20000,"airline", "CK 1", "LK", "CMB", "CLK","Sri Lanka"); - b = new Airline(20001,"airline", "CK 2", "LK", "CMB", "CLK","Sri Lanka"); - c = new Airline(20002,"airline", "CK 3", "LK", "CMB", "CLK","Sri Lanka"); - - airlineList = Arrays.asList(a, b, b); - } - - @Test - public void testSaveAirline() { - Iterable itr = airlineRepository.save(Arrays.asList(a)); - assertThat(itr).isNotNull(); - Airline airline = airlineRepository.findOne(a.getId()); - assertThat(airline).isNotNull(); - assertThat(airline).isEqualTo(a); - assertThat(airline).isNotEqualTo(b); - airlineRepository.delete(a.getId()); - } - - @Test - public void testGetAllAirlines() { - Iterable itr = airlineRepository.save(airlineList); - assertThat(itr).isNotNull(); - Iterable itr2 = airlineRepository.findAll(Arrays.asList(a.getId(), b.getId(), c.getId())); - assertThat(itr2).isNotNull(); - airlineRepository.delete(airlineList); - } - - @Test - public void testDeleteAirlines() { - Iterable itr = airlineRepository.save(Arrays.asList(a)); - assertThat(itr).isNotNull(); - airlineRepository.delete(a.getId()); - Airline airline = airlineRepository.findOne(a.getId()); - assertThat(airline).isNull(); - } - - @Test - public void testGetByName() { - Iterable itr = airlineRepository.save(Arrays.asList(a)); - assertThat(itr).isNotNull(); - List airlines = airlineRepository.findAirlineByName(a.getName()); - assertThat(airlines.size()).isGreaterThanOrEqualTo(1); - assertThat(airlines.get(0)).isEqualTo(a); - airlineRepository.delete(a.getId()); - } - - @After - public void tearDown() { - airlineRepository.delete(airlineList); - } -} diff --git a/couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTests.java b/couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTests.java new file mode 100644 index 00000000..9e01a102 --- /dev/null +++ b/couchbase/example/src/test/java/example/springdata/couchbase/repository/AirlineRepositoryIntegrationTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017 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.springdata.couchbase.repository; + +import static org.assertj.core.api.Assertions.*; + +import example.springdata.couchbase.model.Airline; +import example.springdata.couchbase.util.CouchbaseAvailableRule; + +import java.util.List; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Integration tests showing basic CRUD operations through {@link AirlineRepository}. + * + * @author Chandana Kithalagama + * @author Mark Paluch + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class AirlineRepositoryIntegrationTests { + + @ClassRule // + public static CouchbaseAvailableRule COUCHBASE = CouchbaseAvailableRule.onLocalhost(); + + @Autowired + AirlineRepository airlineRepository; + + @Autowired CouchbaseOperations couchbaseOperations; + + @Before + public void before() { + airlineRepository.findById("LH").ifPresent(couchbaseOperations::remove); + } + + /** + * The derived query executes a N1QL query emitting a single element. + */ + @Test + public void shouldFindAirlineN1ql() { + + Airline airline = airlineRepository.findAirlineByIataCode("TQ"); + + assertThat(airline.getCallsign()).isEqualTo("TXW"); + } + + /** + * The derived query executes a N1QL query and the emitted element is used to invoke + * {@link org.springframework.data.repository.reactive.ReactiveCrudRepository#findById(Object)} for an Id-based + * lookup. Queries without a result do not emit a value. + */ + @Test + public void shouldFindById() { + + Airline airline = airlineRepository.findAirlineByIataCode("TQ"); + + assertThat(airlineRepository.findById(airline.getId())).contains(airline); + assertThat(airlineRepository.findById("unknown")).isEmpty(); + } + + /** + * Find all {@link Airline}s applying the {@code airlines/all} view. + */ + @Test + public void shouldFindByView() { + + List airlines = airlineRepository.findAllBy(); + + assertThat(airlines).hasSize(187); + } + + /** + * Created elements are emitted by the + * {@link org.springframework.data.repository.reactive.ReactiveCrudRepository#save(Object)} method. + */ + @Test + public void shouldCreateAirline() { + + Airline airline = new Airline(); + + airline.setId("LH"); + airline.setIataCode("LH"); + airline.setIcao("DLH"); + airline.setCallsign("Lufthansa"); + airline.setName("Lufthansa"); + airline.setCountry("Germany"); + + airlineRepository.save(airline); + + assertThat(airlineRepository.findById("LH")).contains(airline); + } +} diff --git a/couchbase/pom.xml b/couchbase/pom.xml index 9627c0f4..8e218002 100644 --- a/couchbase/pom.xml +++ b/couchbase/pom.xml @@ -5,11 +5,11 @@ spring-data-couchbase-examples pom - - spring-data-examples - org.springframework.data.examples - 1.0.0.BUILD-SNAPSHOT - + + org.springframework.data.examples + spring-data-examples + 2.0.0.BUILD-SNAPSHOT + Spring Data Couchbase - Examples Sample projects for Spring Data Couchbase @@ -17,14 +17,13 @@ example + util - org.springframework.boot spring-boot-starter-data-couchbase - diff --git a/couchbase/util/pom.xml b/couchbase/util/pom.xml new file mode 100644 index 00000000..d93d75e9 --- /dev/null +++ b/couchbase/util/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + + org.springframework.data.examples + spring-data-couchbase-examples + 2.0.0.BUILD-SNAPSHOT + ../pom.xml + + + spring-data-couchbase-example-utils + Spring Data Couchbase - Example Utilities + + + + + junit + junit + + + + + diff --git a/couchbase/util/src/main/java/example/springdata/couchbase/util/CouchbaseAvailableRule.java b/couchbase/util/src/main/java/example/springdata/couchbase/util/CouchbaseAvailableRule.java new file mode 100644 index 00000000..25279ea3 --- /dev/null +++ b/couchbase/util/src/main/java/example/springdata/couchbase/util/CouchbaseAvailableRule.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017 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.springdata.couchbase.util; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.time.Duration; + +import javax.net.SocketFactory; + +import org.junit.AssumptionViolatedException; +import org.junit.rules.ExternalResource; + +import com.couchbase.client.core.env.DefaultCoreEnvironment; + +/** + * Rule to check Couchbase server availability. If Couchbase is not running, tests are skipped. + * + * @author Mark Paluch + */ +public class CouchbaseAvailableRule extends ExternalResource { + + private final String host; + private final int port; + private final Duration timeout = Duration.ofSeconds(1); + + private CouchbaseAvailableRule(String host, int port) { + this.host = host; + this.port = port; + } + + /** + * Create a new rule requiring Couchbase running on {@code localhost} on + * {@link DefaultCoreEnvironment#BOOTSTRAP_HTTP_DIRECT_PORT}. + * + * @return the test rule. + */ + public static CouchbaseAvailableRule onLocalhost() { + return new CouchbaseAvailableRule("localhost", DefaultCoreEnvironment.BOOTSTRAP_HTTP_DIRECT_PORT); + } + + @Override + protected void before() throws Throwable { + + Socket socket = SocketFactory.getDefault().createSocket(); + try { + socket.connect(new InetSocketAddress(host, port), Math.toIntExact(timeout.toMillis())); + } catch (IOException e) { + throw new AssumptionViolatedException( + String.format("Couchbase not available on on %s:%d. Skipping tests.", host, port), e); + } finally { + socket.close(); + } + } +} diff --git a/pom.xml b/pom.xml index 24fc50f1..7cd25719 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ bom cassandra + couchbase elasticsearch jpa ldap @@ -29,7 +30,6 @@ redis solr web - couchbase