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 extends CouchbasePersistentEntity>, 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, Serializable> entityInformation =
- getEntityInformation(metadata.getDomainType());
- return new SimpleCouchbaseRepository(entityInformation, couchbaseOperations);
+ CouchbaseEntityInformation, Serializable> 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 extends T> 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