diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java index ac706331..84e04f69 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/PartTreeN1qlBasedQuery.java @@ -50,16 +50,21 @@ import org.springframework.util.Assert; public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery { private final PartTree partTree; - private JsonValue placeHolderValues; + private ThreadLocal placeHolderValues; public PartTreeN1qlBasedQuery(CouchbaseQueryMethod queryMethod, CouchbaseOperations couchbaseOperations) { super(queryMethod, couchbaseOperations); this.partTree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType()); + this.placeHolderValues = new ThreadLocal() { + @Override public JsonValue initialValue() { + return JsonArray.create(); + } + }; } @Override protected JsonValue getPlaceholderValues(ParameterAccessor accessor) { - return this.placeHolderValues; + return this.placeHolderValues.get(); } @Override @@ -70,7 +75,7 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery { N1qlCountQueryCreator queryCountCreator = new N1qlCountQueryCreator(partTree, accessor, countFrom, getCouchbaseOperations().getConverter(), getQueryMethod()); Statement statement = queryCountCreator.createQuery(); - this.placeHolderValues = queryCountCreator.getPlaceHolderValues(); + this.placeHolderValues.set(queryCountCreator.getPlaceHolderValues()); return statement; } @@ -83,7 +88,7 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery { DeleteUsePath deleteUsePath = deleteFrom(bucket); N1qlMutateQueryCreator mutateQueryCreator = new N1qlMutateQueryCreator(partTree, accessor, deleteUsePath, getCouchbaseOperations().getConverter(), getQueryMethod()); MutateLimitPath mutateFromWhereOrderBy = mutateQueryCreator.createQuery(); - this.placeHolderValues = mutateQueryCreator.getPlaceHolderValues(); + this.placeHolderValues.set(mutateQueryCreator.getPlaceHolderValues()); if (partTree.isLimiting()) { return mutateFromWhereOrderBy.limit(partTree.getMaxResults()); @@ -99,9 +104,9 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery { } WherePath selectFrom = select.from(bucket); N1qlQueryCreator queryCreator = new N1qlQueryCreator(partTree, accessor, selectFrom, - getCouchbaseOperations().getConverter(), getQueryMethod()); + getCouchbaseOperations().getConverter(), getQueryMethod()); LimitPath selectFromWhereOrderBy = queryCreator.createQuery(); - this.placeHolderValues = queryCreator.getPlaceHolderValues(); + this.placeHolderValues.set(queryCreator.getPlaceHolderValues()); if (queryMethod.isPageQuery()) { Pageable pageable = accessor.getPageable(); diff --git a/src/test/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java new file mode 100644 index 00000000..443027d3 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java @@ -0,0 +1,78 @@ +package org.springframework.data.couchbase; + +import java.util.Collections; +import java.util.List; + +import com.couchbase.client.java.env.CouchbaseEnvironment; +import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.config.CouchbaseConfigurer; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.core.WriteResultChecking; +import org.springframework.data.couchbase.core.query.Consistency; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.data.couchbase.repository.support.IndexManager; + +@Configuration +@EnableCouchbaseRepositories(basePackages = "org.springframework.data.couchbase.repo") +public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfiguration { + + @Bean + public String couchbaseAdminUser() { + return "Administrator"; + } + + @Bean + public String couchbaseAdminPassword() { + return "password"; + } + + @Override + protected List getBootstrapHosts() { + return Collections.singletonList("127.0.0.1"); + } + + @Override + protected String getBucketName() { + return "protected"; + } + + @Override + protected String getBucketPassword() { + return "password"; + } + + // TODO maybe create the bucket if doesn't exist + + @Override + protected CouchbaseEnvironment getEnvironment() { + return DefaultCouchbaseEnvironment.builder().connectTimeout(10000).kvTimeout(10000).queryTimeout(10000) + .viewTimeout(10000).build(); + } + + @Override + public CouchbaseTemplate couchbaseTemplate() throws Exception { + CouchbaseTemplate template = super.couchbaseTemplate(); + template.setWriteResultChecking(WriteResultChecking.LOG); + return template; + } + + // this is for dev so it is ok to auto-create indexes + @Override + public IndexManager indexManager() { + return new IndexManager(); + } + + @Override + protected Consistency getDefaultConsistency() { + return Consistency.READ_YOUR_OWN_WRITES; + } + + @Override + protected CouchbaseConfigurer couchbaseConfigurer() { + return this; + } +} diff --git a/src/test/java/org/springframework/data/couchbase/repo/ItemRepository.java b/src/test/java/org/springframework/data/couchbase/repo/ItemRepository.java new file mode 100644 index 00000000..235d4153 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repo/ItemRepository.java @@ -0,0 +1,15 @@ +package org.springframework.data.couchbase.repo; + +import java.util.List; + +import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; +import org.springframework.data.couchbase.repository.Item; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +@N1qlPrimaryIndexed +public interface ItemRepository extends CrudRepository { + + List findAllByDescriptionNotNull(); +} diff --git a/src/test/java/org/springframework/data/couchbase/repo/PartyPagingRepository.java b/src/test/java/org/springframework/data/couchbase/repo/PartyPagingRepository.java new file mode 100644 index 00000000..307931cc --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repo/PartyPagingRepository.java @@ -0,0 +1,9 @@ +package org.springframework.data.couchbase.repo; + +import org.springframework.data.couchbase.repository.CouchbasePagingAndSortingRepository; +import org.springframework.data.couchbase.repository.Party; +import org.springframework.stereotype.Repository; + +@Repository +public interface PartyPagingRepository extends CouchbasePagingAndSortingRepository { +} diff --git a/src/test/java/org/springframework/data/couchbase/repo/PartyRepository.java b/src/test/java/org/springframework/data/couchbase/repo/PartyRepository.java new file mode 100644 index 00000000..8439b492 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repo/PartyRepository.java @@ -0,0 +1,101 @@ +/* + * Copyright 2017-2020 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 + * + * https://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 org.springframework.data.couchbase.repo; + +import java.util.Date; +import java.util.List; + +import org.springframework.data.couchbase.core.query.*; +import org.springframework.data.couchbase.repository.CouchbaseRepository; +import org.springframework.data.couchbase.repository.Party; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +/** + * @author Simon Baslé + * @author Subhashni Balakrishnan + */ +@Repository +@ViewIndexed(designDoc = "party", viewName = "all") +@N1qlPrimaryIndexed +@N1qlSecondaryIndexed(indexName = "party") +public interface PartyRepository extends CouchbaseRepository { + + List findByAttendeesGreaterThanEqual(int minAttendees); + + List findByName(String name); + + List findByEventDateIs(Date targetDate); + + @View(designDocument = "party", viewName = "byDate") + List findFirst3ByEventDateGreaterThanEqual(Date targetDate); + + List findAllByDescriptionNotNull(); + + long countAllByDescriptionNotNull(); + + @Query("SELECT MAX(attendees) FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") + long findMaxAttendees(); + + @Query("SELECT `desc` FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") + String findSomeString(); + + @Query("SELECT count(*) + 5 FROM #{#n1ql.bucket} WHERE #{#n1ql.filter}") + long countCustomPlusFive(); + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter}") + long countCustom(); + + @Query("SELECT 1 = 1") + boolean justABoolean(); + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `attendees` >= $1") + Page findPartiesWithAttendee(int count, Pageable pageable); + + @Query("#{#n1ql.selectEntity}") + List findParties(Sort sort); + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $included || '%' AND attendees >= $min" + + " AND `desc` NOT LIKE '%' || $excluded || '%'") + List findAllWithNamedParams(@Param("excluded") String ex, @Param("included") String inc, + @Param("min") long minimumAttendees); + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" + + " AND `desc` NOT LIKE '%' || $1 || '%'") + List findAllWithPositionalParams(String ex, String inc, long minimumAttendees); + + @Query("#{#n1ql.delete} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees < $3" + + " AND `desc` NOT LIKE '%' || $1 || '%' #{#n1ql.returning}") + List removeWithPositionalParams(String ex, String inc, long minimumAttendees); + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND `desc` LIKE '%' || $2 || '%' AND attendees >= $3" + + " AND `desc` NOT LIKE '%' || $1 || '%' AND `desc` != \"this is \\\"$excluded\\\"\"") + List findAllWithPositionalParamsAndQuotedNamedParams(@Param("excluded") String ex, + @Param("included") String inc, @Param("min") long min); + + List findByDescriptionOrName(String description, String name); + + List removeByDescriptionOrName(String description, String name); + + @Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and eventDate = $1") + List getByEventDate(Date eventDate); + + List findByDescriptionStartingWith(String description); +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/Item.java b/src/test/java/org/springframework/data/couchbase/repository/Item.java new file mode 100644 index 00000000..7e5b3638 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/Item.java @@ -0,0 +1,38 @@ +package org.springframework.data.couchbase.repository; + +import com.couchbase.client.java.repository.annotation.Field; + +import org.springframework.data.annotation.Id; + +public class Item { + + @Id + public String id; + + @Field("desc") + public String description; + + public Item(String id, String description) { + this.id = id; + this.description = description; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Item item = (Item) o; + + if (!id.equals(item.id)) return false; + return !(description != null ? !description.equals(item.description) : item.description != null); + + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + (description != null ? description.hashCode() : 0); + return result; + } +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryIntegrationTests.java new file mode 100644 index 00000000..f360053a --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/N1qlCouchbaseRepositoryIntegrationTests.java @@ -0,0 +1,277 @@ +/* + * Copyright 2013-2020 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 + * + * https://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 org.springframework.data.couchbase.repository; + +import static org.junit.Assert.*; + +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.dao.DataRetrievalFailureException; +//import org.springframework.data.couchbase.ContainerResourceRunner; +import org.springframework.data.couchbase.IntegrationTestApplicationConfig; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +// must not be in same package as CouchbaseRepository, otherwise AutoWired will fail on couchbaseRepository +import org.springframework.data.couchbase.repo.ItemRepository; +import org.springframework.data.couchbase.repo.PartyRepository; +import org.springframework.data.couchbase.repo.PartyPagingRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.model.MappingInstantiationException; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * This has been copied from 3.2.x for testing backport of fix in DATACOUCH-484 + * This and dependent class have been copied from src/integration to src/test + * and the repository classes have been moved from org.springframework.data.couchbase.repository + * to org.springframework.data.couchbase.repo so that + * org.springframework.data.couchbase.repository.CouchbaseRepository is not instantiated by + * @Autowired + * + * This tests PaginAndSortingRepository features in the Couchbase connector. + * + * @author Simon Baslé + * @author Subhashni Balakrishnan + * @author Michael Reiche + */ +// @RunWith(ContainerResourceRunner.class) +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = IntegrationTestApplicationConfig.class) +@SpringJUnitConfig(IntegrationTestApplicationConfig.class) +@TestExecutionListeners(PartyPopulatorListener.class) +@EnableCouchbaseRepositories +public class N1qlCouchbaseRepositoryIntegrationTests { + + @Autowired private PartyPagingRepository repository; + + @Autowired private PartyRepository partyRepository; + + @Autowired private ItemRepository itemRepository; + + private final String KEY_PARTY = "Party1"; + private final String KEY_ITEM = "Item1"; + + @Before + public void setup() throws Exception { + partyRepository.save(new Party(KEY_PARTY, "partyName", "MatchingDescription", null, 1, null)); + itemRepository.save(new Item(KEY_ITEM, "MatchingDescription")); + } + + @After + public void cleanUp() { + try { + itemRepository.deleteById(KEY_ITEM); + } catch (DataRetrievalFailureException e) {} + try { + partyRepository.deleteById(KEY_PARTY); + } catch (DataRetrievalFailureException e) {} + } + + @Test + public void shouldBeThreadsafe() { + // This doesn't guarantee it, but we should catch most thread issues without + // taking too long here... + int runs = 50; + for (int i = 0; i < runs; i++) { + doShouldBeThreadsafe(); + } + } + + public void doShouldBeThreadsafe() { + int threads = 50; + for (int thread = 0; thread < threads; thread++) { + partyRepository.save(new Party(KEY_PARTY + thread, "Party like it's 199" + thread, "", null, 1, null)); + } + ExecutorService service = Executors.newFixedThreadPool(threads); + List> callables = new ArrayList<>(); + for (int thread = 0; thread < threads; ++thread) { + final int counter = thread; + Callable booleanSupplier = () -> { + String expectedName = "Party like it's 199" + counter; + String foundName = partyRepository.findByName(expectedName).get(0).getName(); + return expectedName.equals(foundName); // should never get false + }; + callables.add(booleanSupplier); + } + try { + List> futures = service.invokeAll(callables); + service.shutdown(); + service.awaitTermination(5, TimeUnit.SECONDS); + for (Future future : futures) { + assertTrue(future.get()); + } + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + fail("Threads failed to run " + e.getMessage()); + } + for (int thread = 0; thread < threads; thread++) { + partyRepository.delete(new Party(KEY_PARTY + thread, "Party like it's 199" + thread, "", null, 1, null)); + } + } + + @Test + public void shouldFindAllWithSort() { + Iterable allByAttendanceDesc = repository.findAll(Sort.by(Sort.Direction.DESC, "attendees")); + long previousAttendance = Long.MAX_VALUE; + for (Party party : allByAttendanceDesc) { + assertTrue(party.getAttendees() <= previousAttendance); + previousAttendance = party.getAttendees(); + } + assertFalse("Expected to find several parties", previousAttendance == Long.MAX_VALUE); + } + + @Test + public void shouldSortOnRenamedFieldIfJsonNameIsProvidedInSort() { + Iterable parties = repository.findAll(Sort.by(Sort.Direction.DESC, "desc")); + String previousDesc = null; + for (Party party : parties) { + if (previousDesc != null) { + assertTrue(party.getDescription().compareTo(previousDesc) <= 0); + } + previousDesc = party.getDescription(); + } + assertNotNull("Expected to find several parties", previousDesc); + } + + @Test + public void shouldSortWithoutCaseSensitivity() { + Iterable parties = repository.findAll(Sort.by(new Sort.Order(Sort.Direction.DESC, "desc").ignoreCase())); + String previousDesc = null; + for (Party party : parties) { + if (previousDesc != null) { + assertTrue(party.getDescription().compareToIgnoreCase(previousDesc) <= 0); + } + previousDesc = party.getDescription(); + } + assertNotNull("Expected to find several parties", previousDesc); + } + + @Test + public void shouldPageThroughEntities() { + Pageable pageable = PageRequest.of(0, 8); + + Page page1 = repository.findAll(pageable); + assertTrue("Query for parties should be atleast 12", page1.getTotalElements() >= 12); + assertEquals(8, page1.getNumberOfElements()); + } + + @Test + public void shouldPageThroughSortedEntities() { + Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees"); + + Page page1 = repository.findAll(pageable); + assertTrue("Query for parties should be atleast 12", page1.getTotalElements() >= 12); + assertEquals(8, page1.getNumberOfElements()); + + List parties = page1.getContent(); + Long previousAttendees = null; + for (Party party : parties) { + if (previousAttendees != null) { + assertTrue(party.getAttendees() <= previousAttendees); + } + previousAttendees = party.getAttendees(); + } + } + + @Test + public void testWrapWhereCriteria() { + List partyList = partyRepository.findByDescriptionOrName("MatchingDescription", "partyName"); + assertTrue(partyList.size() == 1); + } + + @Test + public void shouldPageWithStringBasedQuery() { + Pageable pageable = PageRequest.of(0, 8, Sort.Direction.DESC, "attendees"); + Page page1 = partyRepository.findPartiesWithAttendee(1, pageable); + assertTrue("Query for parties with attendees should be atleast 12", page1.getTotalElements() >= 12); + assertEquals(8, page1.getNumberOfElements()); + + List parties = page1.getContent(); + Long previousAttendees = null; + for (Party party : parties) { + if (previousAttendees != null) { + assertTrue(party.getAttendees() <= previousAttendees); + } + previousAttendees = party.getAttendees(); + } + Page page2 = partyRepository.findPartiesWithAttendee(1, page1.nextPageable()); + assertEquals(8, page2.getNumberOfElements()); + parties = page2.getContent(); + for (Party party : parties) { + if (previousAttendees != null) { + assertTrue(party.getAttendees() <= previousAttendees); + } + previousAttendees = party.getAttendees(); + } + } + + // Fails on deserialization as a different entity item is also present + @Test(expected = MappingInstantiationException.class) + public void shouldFailWithMissingFilterStringBasedQuery() { + Sort sort = Sort.by(Sort.Direction.DESC, "attendees"); + partyRepository.findParties(sort); + } + + @Test + public void testDeleteQuery() { + partyRepository.save(new Party("testDeleteQuery", "delete", "delete", null, 0, null)); + List partyList = partyRepository.removeByDescriptionOrName("delete", "delete"); + assertTrue(partyList.size() == 1); + } + + @Test + public void testSpelDateConvertion() { + final String key = "testSpelDateConvertion"; + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(2018, Calendar.SEPTEMBER, 10); + Date date = cal.getTime(); + partyRepository.save(new Party(key, "", "", date, 0, null)); + List partyList = partyRepository.getByEventDate(date); + assertTrue(partyList.size() == 1); + assertEquals("Key mismatch", partyList.get(0).getKey(), key); + } + + @Test + public void testN1qlQueryWithInvalidValue() { + partyRepository + .save(new Party("testN1qlQueryWithInvalidValue", "", "testN1qlQueryWithInvalidValue", null, 0, null)); + final String description = "testN1qlQueryWithInvalidValue* OR `description` LIKE \"\""; + List partyList = partyRepository.findByDescriptionStartingWith(description); + assertTrue(partyList.size() == 0); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/Party.java b/src/test/java/org/springframework/data/couchbase/repository/Party.java new file mode 100644 index 00000000..7456f86c --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/Party.java @@ -0,0 +1,88 @@ +package org.springframework.data.couchbase.repository; + +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.geo.Point; + +import com.couchbase.client.java.repository.annotation.Field; + +/** + * An entity used to test conversion of parameters in query derivations. + * + * @author Simon Baslé + */ +public class Party { + + @Id + private final String key; + + private final String name; + + @Field("desc") + private final String description; + + private final Date eventDate; + + private final long attendees; + + private final Point location; + + public Party(String key, String name, String description, Date eventDate, long attendees, Point location) { + this.key = key; + this.name = name; + this.description = description; + this.eventDate = eventDate; + this.attendees = attendees; + this.location = location; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Date getEventDate() { + return eventDate; + } + + public long getAttendees() { + return attendees; + } + + public Point getLocation() { + return location; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Party party = (Party) o; + + return key.equals(party.key); + + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public String toString() { + return "Party{" + + "name='" + name + '\'' + + ", eventDate=" + eventDate + + ", location=" + location + + '}'; + } +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/PartyPopulatorListener.java b/src/test/java/org/springframework/data/couchbase/repository/PartyPopulatorListener.java new file mode 100644 index 00000000..20cec7a3 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/PartyPopulatorListener.java @@ -0,0 +1,87 @@ +package org.springframework.data.couchbase.repository; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +import com.couchbase.client.java.Bucket; +import com.couchbase.client.java.PersistTo; +import com.couchbase.client.java.ReplicateTo; +import com.couchbase.client.java.cluster.ClusterInfo; +import com.couchbase.client.java.view.DefaultView; +import com.couchbase.client.java.view.DesignDocument; +import com.couchbase.client.java.view.SpatialView; +import com.couchbase.client.java.view.View; + +import org.springframework.data.couchbase.config.BeanNames; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.geo.Point; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; + +/** + * @author Simon Baslé + */ +public class PartyPopulatorListener extends DependencyInjectionTestExecutionListener { + + @Override + public void beforeTestClass(final TestContext testContext) throws Exception { + Bucket client = (Bucket) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_BUCKET); + ClusterInfo clusterInfo = (ClusterInfo) testContext.getApplicationContext().getBean(BeanNames.COUCHBASE_CLUSTER_INFO); + populateTestData(client, clusterInfo); + createAndWaitForDesignDocs(client); + } + + private void populateTestData(Bucket client, ClusterInfo clusterInfo) { + CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client); + + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.set(Calendar.YEAR, 2015); + cal.set(Calendar.DAY_OF_MONTH, 10); + cal.set(Calendar.MONTH, Calendar.JANUARY); + for (int i = 0; i < 12; i++) { + Party p = new Party("testparty-" + i, "party like it's 199" + i, + "An awesome party, 90's themed, every 10 of the month", + cal.getTime(), 100 + i * 10, + new Point(i, -i)); + template.save(p, PersistTo.MASTER, ReplicateTo.NONE); + cal.roll(Calendar.MONTH, true); + } + + cal.clear(); + cal.set(Calendar.YEAR, 1990); + cal.set(Calendar.MONTH, Calendar.JANUARY); + cal.set(Calendar.DAY_OF_MONTH, 01); + template.save(new Party("aTestParty", "New Year's Eve 90", "Happy New Year", cal.getTime(), 1230000, new Point(100, 100))); + template.save(new Party("lowercaseParty", "lowercase party", "lowercase party", cal.getTime(), 1000, new Point(100, 100))); + template.save(new Party("uppercaseParty", "Uppercase party", "Uppercase party", cal.getTime(), 1000, new Point(100, 100))); + } + + private void createAndWaitForDesignDocs(Bucket client) { + //standard views + List views = new ArrayList(); + String mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " + + "{ emit(doc.eventDate, null); } }"; + views.add(DefaultView.create("byDate", mapFunction, "_count")); + + //create the view design document + DesignDocument designDoc = DesignDocument.create("party", views); + client.bucketManager().upsertDesignDocument(designDoc); + + //geo views + List geoViews = new ArrayList(); + mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " + + "{ emit([doc.location.x, doc.location.y], null); } }"; + geoViews.add(SpatialView.create("byLocation", mapFunction)); + + mapFunction = "function (doc, meta) { if(doc._class == \"" + Party.class.getName() + "\") " + + "{ emit([doc.location.x, doc.location.y, doc.attendees], null); } }"; + geoViews.add(SpatialView.create("byLocationAndAttendees", mapFunction)); + + //create the geo views design document + DesignDocument geoDesignDoc = DesignDocument.create("partyGeo", geoViews); + client.bucketManager().upsertDesignDocument(geoDesignDoc); + } +}