diff --git a/pom.xml b/pom.xml index 0d37407ad..151ad8c7f 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ 1.2.0 2.2.3 1.6.1.RELEASE + 1.3 @@ -87,6 +88,13 @@ ${jackson} + + org.hamcrest + hamcrest-all + ${hamcrest} + test + + diff --git a/src/main/java/org/springframework/data/couchbase/core/view/View.java b/src/main/java/org/springframework/data/couchbase/core/view/View.java new file mode 100644 index 000000000..8cd975d33 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/view/View.java @@ -0,0 +1,35 @@ +package org.springframework.data.couchbase.core.view; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to support the use of Views with Couchbase. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface View { + + /** + * The name of the Design document to use. + * + * This field is mandatory. + * + * @return name of the Design document. + */ + String design(); + + /** + * The name of the View to use. + * + * This field is mandatory. + * + * @return name of the View + */ + String view(); + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java index 6782ea504..3e736eba3 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java @@ -22,6 +22,7 @@ import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.util.Assert; @@ -63,16 +64,17 @@ public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations) * @param domainClass the class for the entity. * @param the value type * @param the id type. + * * @return entity information for that domain class. */ @Override public CouchbaseEntityInformation - getEntityInformation(final Class domainClass) { + getEntityInformation(final Class domainClass) { CouchbasePersistentEntity entity = mappingContext.getPersistentEntity(domainClass); if (entity == null) { throw new MappingException(String.format("Could not lookup mapping metadata for domain class %s!", - domainClass.getName())); + domainClass.getName())); } return new MappingCouchbaseEntityInformation((CouchbasePersistentEntity) entity); } @@ -81,23 +83,26 @@ public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations) * Returns a new Repository based on the metadata. * * @param metadata the repository metadata. + * * @return a new created repository. */ @Override protected Object getTargetRepository(final RepositoryMetadata metadata) { - CouchbaseEntityInformation entityInformation = - getEntityInformation(metadata.getDomainType()); - return new SimpleCouchbaseRepository(entityInformation, couchbaseOperations); + CouchbaseEntityInformation entityInformation = getEntityInformation(metadata.getDomainType()); + final RepositoryInformation repositoryInformation = getRepositoryInformation(metadata, null); + return new SimpleCouchbaseRepository(entityInformation, couchbaseOperations, repositoryInformation); } /** * The base class for this repository. * * @param repositoryMetadata metadata for the repository. + * * @return the base class. */ @Override protected Class getRepositoryBaseClass(final RepositoryMetadata repositoryMetadata) { return SimpleCouchbaseRepository.class; } + } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java index a1adefa43..f651286a0 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java @@ -21,11 +21,15 @@ import com.couchbase.client.protocol.views.ViewResponse; import com.couchbase.client.protocol.views.ViewRow; import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.couchbase.core.view.View; import org.springframework.data.couchbase.repository.CouchbaseRepository; import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation; +import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; import java.io.Serializable; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -46,6 +50,15 @@ public class SimpleCouchbaseRepository implements Co */ private final CouchbaseEntityInformation entityInformation; + /** + * Contains information about this repository. + */ + private final RepositoryInformation repositoryInformation; + + /** + * Convenience to hold all declared methods on the interface since we don't want to iterate every time! + */ + private final Method[] allDeclaredMethods; /** * Create a new Repository. @@ -54,14 +67,19 @@ public class SimpleCouchbaseRepository implements Co * @param couchbaseOperations the reference to the template used. */ public SimpleCouchbaseRepository(final CouchbaseEntityInformation metadata, - final CouchbaseOperations couchbaseOperations) { + final CouchbaseOperations couchbaseOperations, + final RepositoryInformation repositoryInformation) { Assert.notNull(couchbaseOperations); Assert.notNull(metadata); + Assert.notNull(repositoryInformation); entityInformation = metadata; this.couchbaseOperations = couchbaseOperations; + this.repositoryInformation = repositoryInformation; + allDeclaredMethods = ReflectionUtils.getAllDeclaredMethods(repositoryInformation.getRepositoryInterface()); } + @Override public S save(S entity) { Assert.notNull(entity, "Entity must not be null!"); @@ -75,7 +93,7 @@ public Iterable save(Iterable entities) { Assert.notNull(entities, "The given Iterable of entities must not be null!"); List result = new ArrayList(); - for(S entity : entities) { + for (S entity : entities) { save(entity); result.add(entity); } @@ -109,7 +127,7 @@ public void delete(T entity) { @Override public void delete(Iterable entities) { Assert.notNull(entities, "The given Iterable of entities must not be null!"); - for (T entity: entities) { + for (T entity : entities) { couchbaseOperations.remove(entity); } } @@ -117,37 +135,60 @@ public void delete(Iterable entities) { @Override public Iterable findAll() { String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; + String viewName = "all"; - return couchbaseOperations.findByView(design, view, new Query().setReduce(false), - entityInformation.getJavaType()); + final Method findAllMethod = repositoryInformation.getCrudMethods().getFindAllMethod(); + final View view = findAllMethod.getAnnotation(View.class); + if (view != null) { + design = view.design(); + viewName = view.view(); + } + + return couchbaseOperations.findByView(design, viewName, new Query().setReduce(false), entityInformation.getJavaType()); } @Override public Iterable findAll(final Iterable ids) { String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; + String viewName = "all"; + + final Method findAllMethod = repositoryInformation.getCrudMethods().getFindAllMethod(); + final View view = findAllMethod.getAnnotation(View.class); + if (view != null) { + design = view.design(); + viewName = view.view(); + } Query query = new Query(); query.setReduce(false); query.setKeys(ComplexKey.of(ids)); - return couchbaseOperations.findByView(design, view, query, entityInformation.getJavaType()); + return couchbaseOperations.findByView(design, viewName, query, entityInformation.getJavaType()); } @Override public long count() { String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; + String viewName = "all"; + + for (final Method method : allDeclaredMethods) { + if (method.getName().equals("count")) { + final View view = method.getAnnotation(View.class); + if (view != null) { + design = view.design(); + viewName = view.view(); + } + } + } Query query = new Query(); query.setReduce(true); - ViewResponse response = couchbaseOperations.queryView(design, view, query); + ViewResponse response = couchbaseOperations.queryView(design, viewName, query); long count = 0; for (ViewRow row : response) { - count += Long.parseLong(row.getValue()); + count += Long.parseLong(row.getValue()); } return count; @@ -156,12 +197,19 @@ public long count() { @Override public void deleteAll() { String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; + String viewName = "all"; + + final Method deleteMethod = repositoryInformation.getCrudMethods().getDeleteMethod(); + final View view = deleteMethod.getAnnotation(View.class); + if (view != null) { + design = view.design(); + viewName = view.view(); + } Query query = new Query(); query.setReduce(false); - ViewResponse response = couchbaseOperations.queryView(design, view, query); + ViewResponse response = couchbaseOperations.queryView(design, viewName, query); for (ViewRow row : response) { couchbaseOperations.remove(row.getId()); } diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java index 2a8601581..26e62590d 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java @@ -23,9 +23,6 @@ import org.springframework.test.context.TestContext; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -/** - * @author Michael Nitschinger - */ public class CouchbaseRepositoryViewListener extends DependencyInjectionTestExecutionListener { @Override @@ -35,20 +32,18 @@ public void beforeTestClass(final TestContext testContext) throws Exception { createAndWaitForDesignDocs(client); } - private void populateTestData(CouchbaseClient client) { + private void populateTestData(final CouchbaseClient client) { CouchbaseTemplate template = new CouchbaseTemplate(client); - - for(int i=0;i < 100; i++) { - User u = new User("testuser-" + i, "uname" + i); - template.save(u); + for (int i = 0; i < 100; i++) { + template.save(new User("testuser-" + i, "uname-" + i)); } } - private void createAndWaitForDesignDocs(CouchbaseClient client) { - DesignDocument designDoc = new DesignDocument("user"); - String mapFunction = "function (doc, meta) { if(doc._class == " - + "\"org.springframework.data.couchbase.repository.User\") { emit(null, null); } }"; - designDoc.setView(new ViewDesign("all", mapFunction, "_count")); + private void createAndWaitForDesignDocs(final CouchbaseClient client) { + final DesignDocument designDoc = new DesignDocument("user"); + final String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(null, null); } }"; + designDoc.setView(new ViewDesign("customFindAllView", mapFunction, "_count")); + designDoc.setView(new ViewDesign("customCountView", mapFunction, "_count")); client.createDesignDoc(designDoc); } diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java new file mode 100644 index 000000000..1d8cb5298 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java @@ -0,0 +1,58 @@ +package org.springframework.data.couchbase.repository; + +import com.couchbase.client.CouchbaseClient; +import com.couchbase.client.protocol.views.Query; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.couchbase.TestApplicationConfig; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.Iterator; + +import static com.couchbase.client.protocol.views.Stale.FALSE; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TestApplicationConfig.class) +@TestExecutionListeners(CouchbaseRepositoryViewListener.class) +public class CouchbaseRepositoryViewTests { + + @Autowired + private CouchbaseClient client; + + @Autowired + private CouchbaseTemplate template; + + private UserRepositoryCustom repository; + + @Before + public void setup() throws Exception { + repository = new CouchbaseRepositoryFactory(template).getRepository(UserRepositoryCustom.class); + } + + @Test + public void shouldFindAllWithCustomView() { + client.query(client.getView("user", "customFindAllView"), new Query().setStale(FALSE)); + Iterable allUsers = repository.findAll(); + int i = 0; + for (final User allUser : allUsers) { + i++; + } + assertThat(i, is(100)); + } + + @Test + public void shouldCountWithCustomView() { + client.query(client.getView("user", "customCountView"), new Query().setStale(FALSE)); + final long value = repository.count(); + assertThat(value, is(100L)); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java b/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java new file mode 100644 index 000000000..3a4377ff2 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013 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 org.springframework.data.couchbase.repository; + +import com.couchbase.client.CouchbaseClient; +import com.couchbase.client.protocol.views.DesignDocument; +import com.couchbase.client.protocol.views.ViewDesign; +import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; + +/** + * @author Michael Nitschinger + */ +public class SimpleCouchbaseRepositoryListener extends DependencyInjectionTestExecutionListener { + + @Override + public void beforeTestClass(final TestContext testContext) throws Exception { + CouchbaseClient client = (CouchbaseClient) testContext.getApplicationContext().getBean("couchbaseClient"); + populateTestData(client); + createAndWaitForDesignDocs(client); + } + + private void populateTestData(CouchbaseClient client) { + CouchbaseTemplate template = new CouchbaseTemplate(client); + + for (int i = 0; i < 100; i++) { + User u = new User("testuser-" + i, "uname-" + i); + template.save(u); + } + } + + private void createAndWaitForDesignDocs(CouchbaseClient client) { + DesignDocument designDoc = new DesignDocument("user"); + String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(null, null); } }"; + designDoc.setView(new ViewDesign("all", mapFunction, "_count")); + client.createDesignDoc(designDoc); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java b/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java index 90883dae2..d7101206d 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryTests.java @@ -38,14 +38,12 @@ */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = TestApplicationConfig.class) -@TestExecutionListeners(CouchbaseRepositoryViewListener.class) +@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class) public class SimpleCouchbaseRepositoryTests { - @Autowired private CouchbaseClient client; - @Autowired private CouchbaseTemplate template; @@ -75,6 +73,9 @@ public void simpleCrud() { } @Test + /** + * This test uses/assumes a default view called "all" that is configured on Couchbase. + */ public void shouldFindAll() { // do a non-stale query to populate data for testing. client.query(client.getView("user", "all"), new Query().setStale(Stale.FALSE)); diff --git a/src/test/java/org/springframework/data/couchbase/repository/UserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/UserRepository.java index dbbf55a3e..da78c6f76 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/UserRepository.java +++ b/src/test/java/org/springframework/data/couchbase/repository/UserRepository.java @@ -19,6 +19,6 @@ /** * @author Michael Nitschinger */ -public interface UserRepository extends CouchbaseRepository{ +public interface UserRepository extends CouchbaseRepository { } diff --git a/src/test/java/org/springframework/data/couchbase/repository/UserRepositoryCustom.java b/src/test/java/org/springframework/data/couchbase/repository/UserRepositoryCustom.java new file mode 100644 index 000000000..ca417a651 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/UserRepositoryCustom.java @@ -0,0 +1,31 @@ +/* + * Copyright 2013 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 org.springframework.data.couchbase.repository; + +import org.springframework.data.couchbase.core.view.View; + +public interface UserRepositoryCustom extends CouchbaseRepository { + + @Override + @View(design = "user", view = "customFindAllView") + Iterable findAll(); + + @Override + @View(design = "user", view = "customCountView") + long count(); + +}