From 4c320a9473b3938ecb4b7fd56f0f6f87937ea551 Mon Sep 17 00:00:00 2001 From: Michael Nitschinger Date: Wed, 29 May 2013 12:22:27 +0200 Subject: [PATCH] Adding initial CRUD repository implementation. --- .../spring/core/CouchbaseOperations.java | 17 +++ .../spring/core/CouchbaseTemplate.java | 16 ++- .../repository/CouchbaseRepository.java | 12 ++ .../query/CouchbaseEntityInformation.java | 9 ++ .../support/CouchbaseRepositoryFactory.java | 54 +++++++++ .../CouchbaseRepositoryFactoryBean.java | 36 ++++++ .../MappingCouchbaseEntityInformation.java | 42 +++++++ .../support/SimpleCouchbaseRepository.java | 109 ++++++++++++++++++ .../SimpleCouchbaseRepositoryTest.java | 44 +++++++ .../com/couchbase/spring/repository/User.java | 32 +++++ .../spring/repository/UserRepository.java | 6 + 11 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/couchbase/spring/repository/CouchbaseRepository.java create mode 100644 src/main/java/com/couchbase/spring/repository/query/CouchbaseEntityInformation.java create mode 100644 src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactory.java create mode 100644 src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactoryBean.java create mode 100644 src/main/java/com/couchbase/spring/repository/support/MappingCouchbaseEntityInformation.java create mode 100644 src/main/java/com/couchbase/spring/repository/support/SimpleCouchbaseRepository.java create mode 100644 src/test/java/com/couchbase/spring/repository/SimpleCouchbaseRepositoryTest.java create mode 100644 src/test/java/com/couchbase/spring/repository/User.java create mode 100644 src/test/java/com/couchbase/spring/repository/UserRepository.java diff --git a/src/main/java/com/couchbase/spring/core/CouchbaseOperations.java b/src/main/java/com/couchbase/spring/core/CouchbaseOperations.java index 182f3993..6753d842 100644 --- a/src/main/java/com/couchbase/spring/core/CouchbaseOperations.java +++ b/src/main/java/com/couchbase/spring/core/CouchbaseOperations.java @@ -22,6 +22,8 @@ package com.couchbase.spring.core; +import com.couchbase.spring.core.convert.CouchbaseConverter; + import java.util.Collection; public interface CouchbaseOperations { @@ -114,6 +116,14 @@ public interface CouchbaseOperations { */ T findById(String id, Class entityClass); + /** + * Checks if the given document exists. + * + * @param id the unique ID of the document. + * @return whether the document could be found or not. + */ + boolean exists(String id); + /** * Remove the given object from the bucket by id. * @@ -139,4 +149,11 @@ public interface CouchbaseOperations { * @return */ T execute(BucketCallback action); + + /** + * Returns the underlying {@link CouchbaseConverter} + * @return + */ + CouchbaseConverter getConverter(); + } diff --git a/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java b/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java index 798d2a6d..9bc6beaf 100644 --- a/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java +++ b/src/main/java/com/couchbase/spring/core/CouchbaseTemplate.java @@ -189,6 +189,17 @@ public class CouchbaseTemplate implements CouchbaseOperations { } } + @Override + public boolean exists(final String id) { + String result = execute(new BucketCallback() { + @Override + public String doInBucket() { + return (String) client.get(id); + } + }); + return result == null ? false : true; + } + /** * Make sure the given object is not a iterable. * @@ -207,5 +218,8 @@ public class CouchbaseTemplate implements CouchbaseOperations { return resolved == null ? ex : resolved; } - + @Override + public CouchbaseConverter getConverter() { + return couchbaseConverter; + } } diff --git a/src/main/java/com/couchbase/spring/repository/CouchbaseRepository.java b/src/main/java/com/couchbase/spring/repository/CouchbaseRepository.java new file mode 100644 index 00000000..5c1d5595 --- /dev/null +++ b/src/main/java/com/couchbase/spring/repository/CouchbaseRepository.java @@ -0,0 +1,12 @@ +package com.couchbase.spring.repository; + +import org.springframework.data.repository.CrudRepository; + +import java.io.Serializable; + +/** + * Couchbase specific {@link org.springframework.data.repository.Repository} + * interface. + */ +public interface CouchbaseRepository extends CrudRepository { +} diff --git a/src/main/java/com/couchbase/spring/repository/query/CouchbaseEntityInformation.java b/src/main/java/com/couchbase/spring/repository/query/CouchbaseEntityInformation.java new file mode 100644 index 00000000..12f42573 --- /dev/null +++ b/src/main/java/com/couchbase/spring/repository/query/CouchbaseEntityInformation.java @@ -0,0 +1,9 @@ +package com.couchbase.spring.repository.query; + +import org.springframework.data.repository.core.EntityInformation; + +import java.io.Serializable; + + +public interface CouchbaseEntityInformation extends EntityInformation { +} diff --git a/src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactory.java b/src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactory.java new file mode 100644 index 00000000..13ee8ec0 --- /dev/null +++ b/src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactory.java @@ -0,0 +1,54 @@ +package com.couchbase.spring.repository.support; + + +import com.couchbase.spring.core.CouchbaseOperations; +import com.couchbase.spring.core.mapping.CouchbasePersistentEntity; +import com.couchbase.spring.core.mapping.CouchbasePersistentProperty; +import com.couchbase.spring.repository.query.CouchbaseEntityInformation; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.util.Assert; + +import java.io.Serializable; + +/** + * Factory to create {@link CouchbaseRepository} instances. + */ +public class CouchbaseRepositoryFactory extends RepositoryFactorySupport { + + private final CouchbaseOperations couchbaseOperations; + private final MappingContext, + CouchbasePersistentProperty> mappingContext; + + public CouchbaseRepositoryFactory(final CouchbaseOperations couchbaseOperations) { + Assert.notNull(couchbaseOperations); + this.couchbaseOperations = couchbaseOperations; + mappingContext = couchbaseOperations.getConverter().getMappingContext(); + } + + @Override + public CouchbaseEntityInformation + getEntityInformation(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())); + } + return new MappingCouchbaseEntityInformation((CouchbasePersistentEntity) entity); + } + + @Override + protected Object getTargetRepository(RepositoryMetadata metadata) { + CouchbaseEntityInformation entityInformation = + getEntityInformation(metadata.getDomainType()); + return new SimpleCouchbaseRepository(entityInformation, couchbaseOperations); + } + + @Override + protected Class getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) { + return SimpleCouchbaseRepository.class; + } +} diff --git a/src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactoryBean.java b/src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactoryBean.java new file mode 100644 index 00000000..d85e4254 --- /dev/null +++ b/src/main/java/com/couchbase/spring/repository/support/CouchbaseRepositoryFactoryBean.java @@ -0,0 +1,36 @@ +package com.couchbase.spring.repository.support; + + +import com.couchbase.spring.core.CouchbaseOperations; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.util.Assert; + +import java.io.Serializable; + +public class CouchbaseRepositoryFactoryBean, S, ID extends Serializable> extends + RepositoryFactoryBeanSupport { + + private CouchbaseOperations operations; + + public void setCouchbaseOperations(CouchbaseOperations operations) { + this.operations = operations; + setMappingContext(operations.getConverter().getMappingContext()); + } + + @Override + protected RepositoryFactorySupport createRepositoryFactory() { + return getFactoryInstance(operations); + } + + private RepositoryFactorySupport getFactoryInstance(CouchbaseOperations operations) { + return new CouchbaseRepositoryFactory(operations); + } + + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + Assert.notNull(operations, "CouchbaseTemplate must not be null!"); + } +} diff --git a/src/main/java/com/couchbase/spring/repository/support/MappingCouchbaseEntityInformation.java b/src/main/java/com/couchbase/spring/repository/support/MappingCouchbaseEntityInformation.java new file mode 100644 index 00000000..0cd524b1 --- /dev/null +++ b/src/main/java/com/couchbase/spring/repository/support/MappingCouchbaseEntityInformation.java @@ -0,0 +1,42 @@ +package com.couchbase.spring.repository.support; + +import com.couchbase.spring.core.mapping.CouchbasePersistentEntity; +import com.couchbase.spring.core.mapping.CouchbasePersistentProperty; +import com.couchbase.spring.repository.query.CouchbaseEntityInformation; +import org.springframework.data.mapping.model.BeanWrapper; +import org.springframework.data.repository.core.support.AbstractEntityInformation; + +import java.io.Serializable; + + +public class MappingCouchbaseEntityInformation + extends AbstractEntityInformation + implements CouchbaseEntityInformation { + + private final CouchbasePersistentEntity entityMetadata; + + public MappingCouchbaseEntityInformation(CouchbasePersistentEntity entity) { + super(entity.getType()); + entityMetadata = entity; + } + + @Override + public ID getId(T entity) { + CouchbasePersistentProperty idProperty = entityMetadata.getIdProperty(); + + if(idProperty == null) { + return null; + } + + try { + return (ID) BeanWrapper.create(entity, null).getProperty(idProperty); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public Class getIdType() { + return (Class) entityMetadata.getIdProperty().getType(); + } +} diff --git a/src/main/java/com/couchbase/spring/repository/support/SimpleCouchbaseRepository.java b/src/main/java/com/couchbase/spring/repository/support/SimpleCouchbaseRepository.java new file mode 100644 index 00000000..7ca9155b --- /dev/null +++ b/src/main/java/com/couchbase/spring/repository/support/SimpleCouchbaseRepository.java @@ -0,0 +1,109 @@ +package com.couchbase.spring.repository.support; + +import com.couchbase.spring.core.CouchbaseOperations; +import com.couchbase.spring.repository.CouchbaseRepository; +import com.couchbase.spring.repository.query.CouchbaseEntityInformation; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Repository base implementation for Couchbase. + */ +public class SimpleCouchbaseRepository implements CouchbaseRepository { + + private final CouchbaseOperations couchbaseOperations; + private final CouchbaseEntityInformation entityInformation; + + + public SimpleCouchbaseRepository(CouchbaseEntityInformation metadata, CouchbaseOperations couchbaseOperations) { + Assert.notNull(couchbaseOperations); + Assert.notNull(metadata); + + entityInformation = metadata; + this.couchbaseOperations = couchbaseOperations; + } + + @Override + public S save(S entity) { + Assert.notNull(entity, "Entity must not be null!"); + + couchbaseOperations.save(entity); + return entity; + } + + @Override + 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) { + save(entity); + result.add(entity); + } + return result; + } + + @Override + public T findOne(ID id) { + Assert.notNull(id, "The given id must not be null!"); + return couchbaseOperations.findById(id.toString(), entityInformation.getJavaType()); + } + + @Override + public boolean exists(ID id) { + Assert.notNull(id, "The given id must not be null!"); + return couchbaseOperations.exists(id.toString()); + } + + @Override + public void delete(ID id) { + Assert.notNull(id, "The given id must not be null!"); + couchbaseOperations.remove(id.toString()); + } + + @Override + public void delete(T entity) { + Assert.notNull(entity, "The given id must not be null!"); + couchbaseOperations.remove(entity); + } + + @Override + public void delete(Iterable entities) { + Assert.notNull(entities, "The given Iterable of entities must not be null!"); + for (T entity: entities) { + couchbaseOperations.remove(entity); + } + } + + @Override + public Iterable findAll() { + throw new UnsupportedOperationException("findAll is not supported in the current version!"); + } + + @Override + public Iterable findAll(Iterable ids) { + throw new UnsupportedOperationException("findAll is not supported in the current version!"); + } + + @Override + public long count() { + throw new UnsupportedOperationException("count is not supported in the current version!"); + } + + @Override + public void deleteAll() { + throw new UnsupportedOperationException("deleteAll is not supported in the current version!"); + } + + protected CouchbaseOperations getCouchbaseOperations() { + return couchbaseOperations; + } + + protected CouchbaseEntityInformation getEntityInformation() { + return entityInformation; + } + +} diff --git a/src/test/java/com/couchbase/spring/repository/SimpleCouchbaseRepositoryTest.java b/src/test/java/com/couchbase/spring/repository/SimpleCouchbaseRepositoryTest.java new file mode 100644 index 00000000..a6ae5999 --- /dev/null +++ b/src/test/java/com/couchbase/spring/repository/SimpleCouchbaseRepositoryTest.java @@ -0,0 +1,44 @@ +package com.couchbase.spring.repository; + +import com.couchbase.spring.TestApplicationConfig; +import com.couchbase.spring.core.CouchbaseTemplate; +import com.couchbase.spring.repository.support.CouchbaseRepositoryFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static junit.framework.Assert.assertNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TestApplicationConfig.class) +public class SimpleCouchbaseRepositoryTest { + + @Autowired + private CouchbaseTemplate template; + + @Test + public void simpleCrud() { + String key = "my_unique_user_key"; + RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template); + UserRepository repository = factory.getRepository(UserRepository.class); + User instance = new User(key, "foobar"); + repository.save(instance); + + User found = repository.findOne(key); + assertEquals(instance.getKey(), found.getKey()); + assertEquals(instance.getUsername(), found.getUsername()); + + assertTrue(repository.exists(key)); + repository.delete(found); + + assertNull(repository.findOne(key)); + assertFalse(repository.exists(key)); + } + +} diff --git a/src/test/java/com/couchbase/spring/repository/User.java b/src/test/java/com/couchbase/spring/repository/User.java new file mode 100644 index 00000000..f17a5c54 --- /dev/null +++ b/src/test/java/com/couchbase/spring/repository/User.java @@ -0,0 +1,32 @@ +package com.couchbase.spring.repository; + +import org.springframework.data.annotation.Id; + +/** + * Created with IntelliJ IDEA. + * User: michael + * Date: 5/29/13 + * Time: 10:27 AM + * To change this template use File | Settings | File Templates. + */ +public class User { + + @Id + private final String key; + + private final String username; + + public User(String key, String username) { + this.key = key; + this.username = username; + } + + public String getUsername() { + return username; + } + + public String getKey() { + return key; + } + +} diff --git a/src/test/java/com/couchbase/spring/repository/UserRepository.java b/src/test/java/com/couchbase/spring/repository/UserRepository.java new file mode 100644 index 00000000..655a7090 --- /dev/null +++ b/src/test/java/com/couchbase/spring/repository/UserRepository.java @@ -0,0 +1,6 @@ +package com.couchbase.spring.repository; + + +public interface UserRepository extends CouchbaseRepository{ + +}