Skip to content

Commit 48385c9

Browse files
dharrigandaschl
authored andcommitted
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=-
1 parent 8ade609 commit 48385c9

14 files changed

+479
-48
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@
8787
<version>${jackson}</version>
8888
</dependency>
8989

90+
<dependency>
91+
<groupId>org.hamcrest</groupId>
92+
<artifactId>hamcrest-all</artifactId>
93+
<version>${hamcrest}</version>
94+
<scope>test</scope>
95+
</dependency>
96+
9097
</dependencies>
9198

9299
<repositories>

src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ public interface CouchbaseOperations {
110110
* objects. Use the provided {@link #queryView} method for more flexibility and direct access.</p>
111111
*
112112
* @param design the name of the design document.
113-
* @param view the name of the view.
114-
* @param query the Query object to customize the view query.
113+
* @param view the name of the viewName.
114+
* @param query the Query object to customize the viewName query.
115115
* @param entityClass the entity to map to.
116116
* @return the converted collection
117117
*/
@@ -124,12 +124,12 @@ public interface CouchbaseOperations {
124124
* <p>This method is available to ease the working with views by still wrapping exceptions into the Spring
125125
* infrastructure.</p>
126126
*
127-
* <p>It is especially needed if you want to run reduced view queries, because they can't be mapped onto entities
127+
* <p>It is especially needed if you want to run reduced viewName queries, because they can't be mapped onto entities
128128
* directly.</p>
129129
*
130-
* @param design the name of the design document.
131-
* @param view the name of the view.
132-
* @param query the Query object to customize the view query.
130+
* @param design the name of the designDocument document.
131+
* @param view the name of the viewName.
132+
* @param query the Query object to customize the viewName query.
133133
* @return
134134
*/
135135
ViewResponse queryView(String design, String view, Query query);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.couchbase.core.view;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Annotation to support the use of Views with Couchbase.
27+
*
28+
* @author David Harrigan.
29+
*/
30+
@Documented
31+
@Target(ElementType.METHOD)
32+
@Retention(RetentionPolicy.RUNTIME)
33+
public @interface View {
34+
35+
/**
36+
* The name of the Design Document to use.
37+
* <p/>
38+
* This field is mandatory.
39+
*
40+
* @return name of the Design Document.
41+
*/
42+
String designDocument();
43+
44+
/**
45+
* The name of the View to use.
46+
* <p/>
47+
* This field is mandatory.
48+
*
49+
* @return name of the View
50+
*/
51+
String viewName();
52+
53+
}

src/main/java/org/springframework/data/couchbase/repository/CouchbaseRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@
2626
* @author Michael Nitschinger
2727
*/
2828
public interface CouchbaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
29+
2930
}

src/main/java/org/springframework/data/couchbase/repository/support/CouchbaseRepositoryFactory.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
4545
*/
4646
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
4747

48+
/**
49+
* Holds a custom ViewPostProcessor..
50+
*/
51+
private final ViewPostProcessor viewPostProcessor;
52+
4853
/**
4954
* Create a new factory.
5055
*
@@ -55,6 +60,9 @@ public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations)
5560

5661
this.couchbaseOperations = couchbaseOperations;
5762
mappingContext = couchbaseOperations.getConverter().getMappingContext();
63+
viewPostProcessor = ViewPostProcessor.INSTANCE;
64+
65+
addRepositoryProxyPostProcessor(viewPostProcessor);
5866
}
5967

6068
/**
@@ -63,41 +71,45 @@ public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations)
6371
* @param domainClass the class for the entity.
6472
* @param <T> the value type
6573
* @param <ID> the id type.
74+
*
6675
* @return entity information for that domain class.
6776
*/
6877
@Override
69-
public <T, ID extends Serializable> CouchbaseEntityInformation<T, ID>
70-
getEntityInformation(final Class<T> domainClass) {
78+
public <T, ID extends Serializable> CouchbaseEntityInformation<T, ID> getEntityInformation(final Class<T> domainClass) {
7179
CouchbasePersistentEntity<?> entity = mappingContext.getPersistentEntity(domainClass);
7280

7381
if (entity == null) {
74-
throw new MappingException(String.format("Could not lookup mapping metadata for domain class %s!",
75-
domainClass.getName()));
82+
throw new MappingException(String.format("Could not lookup mapping metadata for domain class %s!", domainClass.getName()));
7683
}
84+
7785
return new MappingCouchbaseEntityInformation<T, ID>((CouchbasePersistentEntity<T>) entity);
7886
}
7987

8088
/**
8189
* Returns a new Repository based on the metadata.
8290
*
8391
* @param metadata the repository metadata.
92+
*
8493
* @return a new created repository.
8594
*/
8695
@Override
8796
protected Object getTargetRepository(final RepositoryMetadata metadata) {
88-
CouchbaseEntityInformation<?, Serializable> entityInformation =
89-
getEntityInformation(metadata.getDomainType());
90-
return new SimpleCouchbaseRepository(entityInformation, couchbaseOperations);
97+
CouchbaseEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
98+
final SimpleCouchbaseRepository simpleCouchbaseRepository = new SimpleCouchbaseRepository(entityInformation, couchbaseOperations);
99+
simpleCouchbaseRepository.setViewMetadataProvider(viewPostProcessor.getViewMetadataProvider());
100+
return simpleCouchbaseRepository;
91101
}
92102

93103
/**
94104
* The base class for this repository.
95105
*
96106
* @param repositoryMetadata metadata for the repository.
107+
*
97108
* @return the base class.
98109
*/
99110
@Override
100111
protected Class<?> getRepositoryBaseClass(final RepositoryMetadata repositoryMetadata) {
101112
return SimpleCouchbaseRepository.class;
102113
}
114+
103115
}

src/main/java/org/springframework/data/couchbase/repository/support/SimpleCouchbaseRepository.java

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.couchbase.client.protocol.views.ViewResponse;
2222
import com.couchbase.client.protocol.views.ViewRow;
2323
import org.springframework.data.couchbase.core.CouchbaseOperations;
24+
import org.springframework.data.couchbase.core.view.View;
2425
import org.springframework.data.couchbase.repository.CouchbaseRepository;
2526
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
2627
import org.springframework.util.Assert;
@@ -46,22 +47,34 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
4647
*/
4748
private final CouchbaseEntityInformation<T, String> entityInformation;
4849

50+
/**
51+
* Custom ViewMetadataProvider.
52+
*/
53+
private ViewMetadataProvider viewMetadataProvider;
4954

5055
/**
5156
* Create a new Repository.
5257
*
5358
* @param metadata the Metadata for the entity.
5459
* @param couchbaseOperations the reference to the template used.
5560
*/
56-
public SimpleCouchbaseRepository(final CouchbaseEntityInformation<T, String> metadata,
57-
final CouchbaseOperations couchbaseOperations) {
61+
public SimpleCouchbaseRepository(final CouchbaseEntityInformation<T, String> metadata, final CouchbaseOperations couchbaseOperations) {
5862
Assert.notNull(couchbaseOperations);
5963
Assert.notNull(metadata);
6064

6165
entityInformation = metadata;
6266
this.couchbaseOperations = couchbaseOperations;
6367
}
6468

69+
/**
70+
* Configures a custom {@link ViewMetadataProvider} to be used to detect {@link View}s to be applied to queries.
71+
*
72+
* @param viewMetadataProvider that is used to lookup any annotated View on a query method.
73+
*/
74+
public void setViewMetadataProvider(final ViewMetadataProvider viewMetadataProvider) {
75+
this.viewMetadataProvider = viewMetadataProvider;
76+
}
77+
6578
@Override
6679
public <S extends T> S save(S entity) {
6780
Assert.notNull(entity, "Entity must not be null!");
@@ -75,7 +88,7 @@ public <S extends T> Iterable<S> save(Iterable<S> entities) {
7588
Assert.notNull(entities, "The given Iterable of entities must not be null!");
7689

7790
List<S> result = new ArrayList<S>();
78-
for(S entity : entities) {
91+
for (S entity : entities) {
7992
save(entity);
8093
result.add(entity);
8194
}
@@ -109,59 +122,50 @@ public void delete(T entity) {
109122
@Override
110123
public void delete(Iterable<? extends T> entities) {
111124
Assert.notNull(entities, "The given Iterable of entities must not be null!");
112-
for (T entity: entities) {
125+
for (T entity : entities) {
113126
couchbaseOperations.remove(entity);
114127
}
115128
}
116129

117130
@Override
118131
public Iterable<T> findAll() {
119-
String design = entityInformation.getJavaType().getSimpleName().toLowerCase();
120-
String view = "all";
121-
122-
return couchbaseOperations.findByView(design, view, new Query().setReduce(false),
123-
entityInformation.getJavaType());
132+
final ResolvedView resolvedView = determineView();
133+
return couchbaseOperations.findByView(resolvedView.getDesignDocument(), resolvedView.getViewName(), new Query().setReduce(false), entityInformation.getJavaType());
124134
}
125135

126136
@Override
127137
public Iterable<T> findAll(final Iterable<ID> ids) {
128-
String design = entityInformation.getJavaType().getSimpleName().toLowerCase();
129-
String view = "all";
130-
131138
Query query = new Query();
132139
query.setReduce(false);
133140
query.setKeys(ComplexKey.of(ids));
134141

135-
return couchbaseOperations.findByView(design, view, query, entityInformation.getJavaType());
142+
final ResolvedView resolvedView = determineView();
143+
return couchbaseOperations.findByView(resolvedView.getDesignDocument(), resolvedView.getViewName(), query, entityInformation.getJavaType());
136144
}
137145

138146
@Override
139147
public long count() {
140-
String design = entityInformation.getJavaType().getSimpleName().toLowerCase();
141-
String view = "all";
142-
143148
Query query = new Query();
144149
query.setReduce(true);
145150

146-
ViewResponse response = couchbaseOperations.queryView(design, view, query);
151+
final ResolvedView resolvedView = determineView();
152+
ViewResponse response = couchbaseOperations.queryView(resolvedView.getDesignDocument(), resolvedView.getViewName(), query);
147153

148154
long count = 0;
149155
for (ViewRow row : response) {
150-
count += Long.parseLong(row.getValue());
156+
count += Long.parseLong(row.getValue());
151157
}
152158

153159
return count;
154160
}
155161

156162
@Override
157163
public void deleteAll() {
158-
String design = entityInformation.getJavaType().getSimpleName().toLowerCase();
159-
String view = "all";
160-
161164
Query query = new Query();
162165
query.setReduce(false);
163166

164-
ViewResponse response = couchbaseOperations.queryView(design, view, query);
167+
final ResolvedView resolvedView = determineView();
168+
ViewResponse response = couchbaseOperations.queryView(resolvedView.getDesignDocument(), resolvedView.getViewName(), query);
165169
for (ViewRow row : response) {
166170
couchbaseOperations.remove(row.getId());
167171
}
@@ -185,4 +189,48 @@ protected CouchbaseEntityInformation<T, String> getEntityInformation() {
185189
return entityInformation;
186190
}
187191

192+
/**
193+
* Resolve a View based upon:
194+
* <p/>
195+
* 1. Any @View annotation that is present
196+
* 2. If none are found, default designDocument to be the entity name (lowercase) and viewName to be "all".
197+
*
198+
* @return ResolvedView containing the designDocument and viewName.
199+
*/
200+
private ResolvedView determineView() {
201+
String designDocument = entityInformation.getJavaType().getSimpleName().toLowerCase();
202+
String viewName = "all";
203+
204+
final View view = viewMetadataProvider.getView();
205+
206+
if (view != null) {
207+
designDocument = view.designDocument();
208+
viewName = view.viewName();
209+
}
210+
211+
return new ResolvedView(designDocument, viewName);
212+
}
213+
214+
/**
215+
* Simple holder to allow an easier exchange of information.
216+
*/
217+
private final class ResolvedView {
218+
219+
private final String designDocument;
220+
private final String viewName;
221+
222+
public ResolvedView(final String designDocument, final String viewName) {
223+
this.designDocument = designDocument;
224+
this.viewName = viewName;
225+
}
226+
227+
private String getDesignDocument() {
228+
return designDocument;
229+
}
230+
231+
private String getViewName() {
232+
return viewName;
233+
}
234+
}
235+
188236
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.repository.support;
17+
18+
import org.springframework.data.couchbase.core.view.View;
19+
20+
/**
21+
* Interface to abstract {@link ViewMetadataProvider} that provides {@link View}s to be used for query execution.
22+
*
23+
* @author David Harrigan.
24+
*/
25+
public interface ViewMetadataProvider {
26+
27+
/**
28+
* Returns the {@link View} to be used.
29+
*
30+
* @return the View, or null if the method hasn't been annotated with @View.
31+
*/
32+
View getView();
33+
34+
}

0 commit comments

Comments
 (0)