Skip to content

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

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
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,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
Expand Up @@ -110,8 +110,8 @@ public interface CouchbaseOperations {
* objects. Use the provided {@link #queryView} method for more flexibility and direct access.</p>
*
* @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
*/
Expand All @@ -124,12 +124,12 @@ public interface CouchbaseOperations {
* <p>This method is available to ease the working with views by still wrapping exceptions into the Spring
* infrastructure.</p>
*
* <p>It is especially needed if you want to run reduced view queries, because they can't be mapped onto entities
* <p>It is especially needed if you want to run reduced viewName queries, because they can't be mapped onto entities
* directly.</p>
*
* @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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p/>
* This field is mandatory.
*
* @return name of the Design Document.
*/
String designDocument();

/**
* The name of the View to use.
* <p/>
* This field is mandatory.
*
* @return name of the View
*/
String viewName();

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
* @author Michael Nitschinger
*/
public interface CouchbaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -55,6 +60,9 @@ public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations)

this.couchbaseOperations = couchbaseOperations;
mappingContext = couchbaseOperations.getConverter().getMappingContext();
viewPostProcessor = ViewPostProcessor.INSTANCE;

addRepositoryProxyPostProcessor(viewPostProcessor);
}

/**
Expand All @@ -63,41 +71,45 @@ 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) {
public <T, ID extends Serializable> CouchbaseEntityInformation<T, ID> 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()));
throw new MappingException(String.format("Could not lookup mapping metadata for domain class %s!", domainClass.getName()));
}

return new MappingCouchbaseEntityInformation<T, ID>((CouchbasePersistentEntity<T>) entity);
}

/**
* 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,22 +47,34 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
*/
private final CouchbaseEntityInformation<T, String> entityInformation;

/**
* Custom ViewMetadataProvider.
*/
private ViewMetadataProvider viewMetadataProvider;

/**
* Create a new Repository.
*
* @param metadata the Metadata for the entity.
* @param couchbaseOperations the reference to the template used.
*/
public SimpleCouchbaseRepository(final CouchbaseEntityInformation<T, String> metadata,
final CouchbaseOperations couchbaseOperations) {
public SimpleCouchbaseRepository(final CouchbaseEntityInformation<T, String> metadata, final CouchbaseOperations couchbaseOperations) {
Assert.notNull(couchbaseOperations);
Assert.notNull(metadata);

entityInformation = metadata;
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 extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
Expand All @@ -75,7 +88,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,59 +122,50 @@ 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";

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<T> findAll(final Iterable<ID> 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;
}

@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());
}
Expand All @@ -185,4 +189,48 @@ protected CouchbaseEntityInformation<T, String> getEntityInformation() {
return entityInformation;
}

/**
* Resolve a View based upon:
* <p/>
* 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;
}
}

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

}
Loading