From 07dd60a3c1d354f9817051e08b9ad1e0f1736455 Mon Sep 17 00:00:00 2001 From: David Harrigan Date: Thu, 10 Oct 2013 16:39:00 +0100 Subject: [PATCH] DATACOUCH-16 - Allow View customization through @View annotations. An initial first attempt at allowing for basic View customization using the @View annotation. For now, it does not support "dynamic" finders, such as findByUsername, but I'm sure it will come shortly aftewards. Also added in hamcrest library to gradually transistion deprecated JUnit methods to a better assertion library :-) -=david=- --- pom.xml | 7 ++ .../couchbase/core/CouchbaseOperations.java | 12 +- .../data/couchbase/core/view/View.java | 53 +++++++++ .../repository/CouchbaseRepository.java | 1 + .../support/CouchbaseRepositoryFactory.java | 26 +++-- .../support/SimpleCouchbaseRepository.java | 92 +++++++++++---- .../support/ViewMetadataProvider.java | 34 ++++++ .../repository/support/ViewPostProcessor.java | 109 ++++++++++++++++++ .../CouchbaseRepositoryViewListener.java | 21 ++-- .../CouchbaseRepositoryViewTests.java | 75 ++++++++++++ .../repository/CustomUserRepository.java | 34 ++++++ .../SimpleCouchbaseRepositoryListener.java | 54 +++++++++ .../SimpleCouchbaseRepositoryTests.java | 7 +- .../couchbase/repository/UserRepository.java | 2 +- 14 files changed, 479 insertions(+), 48 deletions(-) create mode 100644 src/main/java/org/springframework/data/couchbase/core/view/View.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/support/ViewMetadataProvider.java create mode 100644 src/main/java/org/springframework/data/couchbase/repository/support/ViewPostProcessor.java create mode 100644 src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java create mode 100644 src/test/java/org/springframework/data/couchbase/repository/CustomUserRepository.java create mode 100644 src/test/java/org/springframework/data/couchbase/repository/SimpleCouchbaseRepositoryListener.java diff --git a/pom.xml b/pom.xml index 0d37407ad..c4a640c49 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,13 @@ ${jackson} + + org.hamcrest + hamcrest-all + ${hamcrest} + test + + diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java index 6b4deccbf..ff812dcbd 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java @@ -110,8 +110,8 @@ public interface CouchbaseOperations { * objects. Use the provided {@link #queryView} method for more flexibility and direct access.

* * @param design the name of the design document. - * @param view the name of the view. - * @param query the Query object to customize the view query. + * @param view the name of the viewName. + * @param query the Query object to customize the viewName query. * @param entityClass the entity to map to. * @return the converted collection */ @@ -124,12 +124,12 @@ public interface CouchbaseOperations { *

This method is available to ease the working with views by still wrapping exceptions into the Spring * infrastructure.

* - *

It is especially needed if you want to run reduced view queries, because they can't be mapped onto entities + *

It is especially needed if you want to run reduced viewName queries, because they can't be mapped onto entities * directly.

* - * @param design the name of the design document. - * @param view the name of the view. - * @param query the Query object to customize the view query. + * @param design the name of the designDocument document. + * @param view the name of the viewName. + * @param query the Query object to customize the viewName query. * @return */ ViewResponse queryView(String design, String view, Query query); 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..d11fdb088 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/view/View.java @@ -0,0 +1,53 @@ +/* + * 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.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. + * + * @author David Harrigan. + */ +@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 designDocument(); + + /** + * The name of the View to use. + *

+ * This field is mandatory. + * + * @return name of the View + */ + String viewName(); + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java b/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java index 9c08f6906..d9ebf9e46 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java +++ b/src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java @@ -26,4 +26,5 @@ * @author Michael Nitschinger */ public interface CouchbaseRepository extends CrudRepository { + } 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..24f5a357f 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 @@ -45,6 +45,11 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { */ private final MappingContext, CouchbasePersistentProperty> mappingContext; + /** + * Holds a custom ViewPostProcessor.. + */ + private final ViewPostProcessor viewPostProcessor; + /** * Create a new factory. * @@ -55,6 +60,9 @@ public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations) this.couchbaseOperations = couchbaseOperations; mappingContext = couchbaseOperations.getConverter().getMappingContext(); + viewPostProcessor = ViewPostProcessor.INSTANCE; + + addRepositoryProxyPostProcessor(viewPostProcessor); } /** @@ -63,17 +71,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) { + public CouchbaseEntityInformation 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())); + throw new MappingException(String.format("Could not lookup mapping metadata for domain class %s!", domainClass.getName())); } + return new MappingCouchbaseEntityInformation((CouchbasePersistentEntity) entity); } @@ -81,23 +89,27 @@ 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 SimpleCouchbaseRepository simpleCouchbaseRepository = new SimpleCouchbaseRepository(entityInformation, couchbaseOperations); + simpleCouchbaseRepository.setViewMetadataProvider(viewPostProcessor.getViewMetadataProvider()); + return simpleCouchbaseRepository; } /** * 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..fea6205e7 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,6 +21,7 @@ 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.util.Assert; @@ -46,6 +47,10 @@ public class SimpleCouchbaseRepository implements Co */ private final CouchbaseEntityInformation entityInformation; + /** + * Custom ViewMetadataProvider. + */ + private ViewMetadataProvider viewMetadataProvider; /** * Create a new Repository. @@ -53,8 +58,7 @@ public class SimpleCouchbaseRepository implements Co * @param metadata the Metadata for the entity. * @param couchbaseOperations the reference to the template used. */ - public SimpleCouchbaseRepository(final CouchbaseEntityInformation metadata, - final CouchbaseOperations couchbaseOperations) { + public SimpleCouchbaseRepository(final CouchbaseEntityInformation metadata, final CouchbaseOperations couchbaseOperations) { Assert.notNull(couchbaseOperations); Assert.notNull(metadata); @@ -62,6 +66,15 @@ public SimpleCouchbaseRepository(final CouchbaseEntityInformation met this.couchbaseOperations = couchbaseOperations; } + /** + * Configures a custom {@link ViewMetadataProvider} to be used to detect {@link View}s to be applied to queries. + * + * @param viewMetadataProvider that is used to lookup any annotated View on a query method. + */ + public void setViewMetadataProvider(final ViewMetadataProvider viewMetadataProvider) { + this.viewMetadataProvider = viewMetadataProvider; + } + @Override public S save(S entity) { Assert.notNull(entity, "Entity must not be null!"); @@ -75,7 +88,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,45 +122,38 @@ 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); } } @Override public Iterable findAll() { - String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; - - return couchbaseOperations.findByView(design, view, new Query().setReduce(false), - entityInformation.getJavaType()); + final ResolvedView resolvedView = determineView(); + return couchbaseOperations.findByView(resolvedView.getDesignDocument(), resolvedView.getViewName(), new Query().setReduce(false), entityInformation.getJavaType()); } @Override public Iterable findAll(final Iterable ids) { - String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; - Query query = new Query(); query.setReduce(false); query.setKeys(ComplexKey.of(ids)); - return couchbaseOperations.findByView(design, view, query, entityInformation.getJavaType()); + final ResolvedView resolvedView = determineView(); + return couchbaseOperations.findByView(resolvedView.getDesignDocument(), resolvedView.getViewName(), query, entityInformation.getJavaType()); } @Override public long count() { - String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; - Query query = new Query(); query.setReduce(true); - ViewResponse response = couchbaseOperations.queryView(design, view, query); + final ResolvedView resolvedView = determineView(); + ViewResponse response = couchbaseOperations.queryView(resolvedView.getDesignDocument(), resolvedView.getViewName(), query); long count = 0; for (ViewRow row : response) { - count += Long.parseLong(row.getValue()); + count += Long.parseLong(row.getValue()); } return count; @@ -155,13 +161,11 @@ public long count() { @Override public void deleteAll() { - String design = entityInformation.getJavaType().getSimpleName().toLowerCase(); - String view = "all"; - Query query = new Query(); query.setReduce(false); - ViewResponse response = couchbaseOperations.queryView(design, view, query); + final ResolvedView resolvedView = determineView(); + ViewResponse response = couchbaseOperations.queryView(resolvedView.getDesignDocument(), resolvedView.getViewName(), query); for (ViewRow row : response) { couchbaseOperations.remove(row.getId()); } @@ -185,4 +189,48 @@ protected CouchbaseEntityInformation getEntityInformation() { return entityInformation; } + /** + * Resolve a View based upon: + *

+ * 1. Any @View annotation that is present + * 2. If none are found, default designDocument to be the entity name (lowercase) and viewName to be "all". + * + * @return ResolvedView containing the designDocument and viewName. + */ + private ResolvedView determineView() { + String designDocument = entityInformation.getJavaType().getSimpleName().toLowerCase(); + String viewName = "all"; + + final View view = viewMetadataProvider.getView(); + + if (view != null) { + designDocument = view.designDocument(); + viewName = view.viewName(); + } + + return new ResolvedView(designDocument, viewName); + } + + /** + * Simple holder to allow an easier exchange of information. + */ + private final class ResolvedView { + + private final String designDocument; + private final String viewName; + + public ResolvedView(final String designDocument, final String viewName) { + this.designDocument = designDocument; + this.viewName = viewName; + } + + private String getDesignDocument() { + return designDocument; + } + + private String getViewName() { + return viewName; + } + } + } diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/ViewMetadataProvider.java b/src/main/java/org/springframework/data/couchbase/repository/support/ViewMetadataProvider.java new file mode 100644 index 000000000..a47384f11 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/support/ViewMetadataProvider.java @@ -0,0 +1,34 @@ +/* + * 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.support; + +import org.springframework.data.couchbase.core.view.View; + +/** + * Interface to abstract {@link ViewMetadataProvider} that provides {@link View}s to be used for query execution. + * + * @author David Harrigan. + */ +public interface ViewMetadataProvider { + + /** + * Returns the {@link View} to be used. + * + * @return the View, or null if the method hasn't been annotated with @View. + */ + View getView(); + +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/support/ViewPostProcessor.java b/src/main/java/org/springframework/data/couchbase/repository/support/ViewPostProcessor.java new file mode 100644 index 000000000..da6c54ce6 --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/repository/support/ViewPostProcessor.java @@ -0,0 +1,109 @@ +/* + * 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.support; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.interceptor.ExposeInvocationInterceptor; +import org.springframework.core.NamedThreadLocal; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.couchbase.core.view.View; +import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * {@link RepositoryProxyPostProcessor} that sets up an interceptor to read {@link View} information from the + * invoked method. This is necessary to allow redeclaration of CRUD methods in repository interfaces and configure + * view information on them. + * + * @author David Harrigan. + */ +public enum ViewPostProcessor implements RepositoryProxyPostProcessor { + + INSTANCE; + + private static final ThreadLocal> VIEW_METADATA = new NamedThreadLocal>("View Metadata"); + + @Override + public void postProcess(final ProxyFactory factory) { + factory.addAdvice(ExposeInvocationInterceptor.INSTANCE); + factory.addAdvice(ViewInterceptor.INSTANCE); + } + + public ViewMetadataProvider getViewMetadataProvider() { + return ThreadBoundViewMetadata.INSTANCE; + } + + /** + * {@link MethodInterceptor} to inspect the currently invoked {@link Method} for a {@link View} annotation. + *

+ * If a View annotation is found, it will bind it to a locally held ThreadLocal for later lookup in the + * SimpleCouchbaseRepository class. + * + * @author David Harrigan. + */ + static enum ViewInterceptor implements MethodInterceptor { + + INSTANCE; + + @Override + public Object invoke(final MethodInvocation invocation) throws Throwable { + + final View view = AnnotationUtils.getAnnotation(invocation.getMethod(), View.class); + if (view != null) { + Map map = VIEW_METADATA.get(); + if (map == null) { + map = new HashMap(); + VIEW_METADATA.set(map); + } + map.put(invocation.getMethod(), view); + } + try { + return invocation.proceed(); + } finally { + VIEW_METADATA.remove(); + } + + } + + } + + /** + * {@link ViewMetadataProvider} that looks up a bound View from a locally held ThreadLocal, using + * the current method invocationas as the key. If not bound View is found, a null is returned. + * + * @author David Harrigan. + */ + private static enum ThreadBoundViewMetadata implements ViewMetadataProvider { + + INSTANCE; + + @Override + public View getView() { + final MethodInvocation invocation = ExposeInvocationInterceptor.currentInvocation(); + final Map map = VIEW_METADATA.get(); + return (map == null) ? null : (View) map.get(invocation.getMethod()); + } + + } + + +} 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..ada4d1450 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewListener.java @@ -35,20 +35,23 @@ 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) { + private void createAndWaitForDesignDocs(final 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")); + String mapFunction = "function (doc, meta) { if(doc._class == \"org.springframework.data.couchbase.repository.User\") { emit(null, null); } }"; + designDoc.setView(new ViewDesign("customFindAllView", mapFunction, "_count")); + + client.createDesignDoc(designDoc); + + designDoc = new DesignDocument("userCustom"); + 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..15a6dae1f --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryViewTests.java @@ -0,0 +1,75 @@ +/* + * 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.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 static com.couchbase.client.protocol.views.Stale.FALSE; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author David Harrigan + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TestApplicationConfig.class) +@TestExecutionListeners(CouchbaseRepositoryViewListener.class) +public class CouchbaseRepositoryViewTests { + + @Autowired + private CouchbaseClient client; + + @Autowired + private CouchbaseTemplate template; + + private CustomUserRepository repository; + + @Before + public void setup() throws Exception { + repository = new CouchbaseRepositoryFactory(template).getRepository(CustomUserRepository.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("userCustom", "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/CustomUserRepository.java b/src/test/java/org/springframework/data/couchbase/repository/CustomUserRepository.java new file mode 100644 index 000000000..ee1081e08 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/repository/CustomUserRepository.java @@ -0,0 +1,34 @@ +/* + * 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; + +/** + * @author David Harrigan + */ +public interface CustomUserRepository extends CouchbaseRepository { + + @Override + @View(designDocument = "user", viewName = "customFindAllView") + Iterable findAll(); + + @Override + @View(designDocument = "userCustom", viewName = "customCountView") + long count(); + +} 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..cc7d75d5f 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 viewName 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 { }