Skip to content

DATACOUCH-16 - Allow View customization through @View annotations. #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<couchbase>1.2.0</couchbase>
<jackson>2.2.3</jackson>
<springdata.commons>1.6.1.RELEASE</springdata.commons>
<hamcrest>1.3</hamcrest>
</properties>

<dependencies>
Expand Down Expand Up @@ -87,6 +88,13 @@
<version>${jackson}</version>
</dependency>

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>${hamcrest}</version>
<scope>test</scope>
</dependency>

</dependencies>

<repositories>
Expand Down
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -63,16 +64,17 @@ public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations)
* @param domainClass the class for the entity.
* @param <T> the value type
* @param <ID> the id type.
*
* @return entity information for that domain class.
*/
@Override
public <T, ID extends Serializable> CouchbaseEntityInformation<T, ID>
getEntityInformation(final Class<T> domainClass) {
getEntityInformation(final Class<T> 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<T, ID>((CouchbasePersistentEntity<T>) entity);
}
Expand All @@ -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<?, Serializable> entityInformation =
getEntityInformation(metadata.getDomainType());
return new SimpleCouchbaseRepository(entityInformation, couchbaseOperations);
CouchbaseEntityInformation<?, Serializable> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -46,6 +50,15 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
*/
private final CouchbaseEntityInformation<T, String> 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.
Expand All @@ -54,14 +67,19 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
* @param couchbaseOperations the reference to the template used.
*/
public SimpleCouchbaseRepository(final CouchbaseEntityInformation<T, String> 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 extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
Expand All @@ -75,7 +93,7 @@ public <S extends T> Iterable<S> save(Iterable<S> entities) {
Assert.notNull(entities, "The given Iterable of entities must not be null!");

List<S> result = new ArrayList<S>();
for(S entity : entities) {
for (S entity : entities) {
save(entity);
result.add(entity);
}
Expand Down Expand Up @@ -109,45 +127,68 @@ 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<T> 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<T> findAll(final Iterable<ID> 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;
Expand All @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<User> 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));
}

}
Loading