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