diff --git a/pom.xml b/pom.xml index 06c7d4508..b96e01410 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,27 @@ ${springdata.commons} + + com.querydsl.contrib + querydsl-elasticsearch + ${querydsl} + true + + + + com.querydsl + querydsl-apt + ${querydsl} + provided + + + + javax.annotation + jsr250-api + 1.0 + true + + commons-lang @@ -139,6 +160,30 @@ + + com.mysema.maven + apt-maven-plugin + ${apt} + + + com.querydsl + querydsl-apt + ${querydsl} + + + + + generate-test-sources + + test-process + + + target/generated-test-sources + org.springframework.data.elasticsearch.repository.support.ElasticsearchAnnotationProcessor + + + + org.apache.maven.plugins maven-assembly-plugin diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java index 53ac150d2..6f950ef44 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.core; import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.Client; import org.springframework.data.domain.Page; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; @@ -41,6 +42,16 @@ public interface ElasticsearchOperations { */ ElasticsearchConverter getElasticsearchConverter(); + /** + * @return Client in use + */ + Client getClient(); + + /** + * @return ResultsMapper in use + */ + ResultsMapper getResultsMapper(); + /** * Create an index for a class * diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index c431c20dd..396de8b92 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -52,6 +52,7 @@ import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.action.suggest.SuggestRequest; import org.elasticsearch.action.suggest.SuggestRequestBuilder; import org.elasticsearch.action.suggest.SuggestResponse; import org.elasticsearch.action.update.UpdateRequestBuilder; @@ -64,6 +65,7 @@ import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.collect.Maps; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.query.FilterBuilder; import org.elasticsearch.index.query.QueryBuilder; @@ -232,6 +234,16 @@ public ElasticsearchConverter getElasticsearchConverter() { return elasticsearchConverter; } + @Override + public Client getClient() { + return client; + } + + @Override + public ResultsMapper getResultsMapper() { + return resultsMapper; + } + @Override public T queryForObject(GetQuery query, Class clazz) { return queryForObject(query, clazz, resultsMapper); @@ -1151,10 +1163,6 @@ private static String[] toArray(List values) { return values.toArray(valuesAsArray); } - protected ResultsMapper getResultsMapper() { - return resultsMapper; - } - private boolean isDocument(Class clazz) { return clazz.isAnnotationPresent(Document.class); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchAnnotationProcessor.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchAnnotationProcessor.java new file mode 100644 index 000000000..755570ba9 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchAnnotationProcessor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015 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.elasticsearch.repository.support; + +import java.util.Collections; + +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.tools.Diagnostic; + +import com.querydsl.core.annotations.QueryEmbeddable; +import com.querydsl.core.annotations.QueryEmbedded; +import com.querydsl.core.annotations.QueryEntities; +import com.querydsl.core.annotations.QuerySupertype; +import com.querydsl.core.annotations.QueryTransient; +import com.querydsl.apt.AbstractQuerydslProcessor; +import com.querydsl.apt.Configuration; +import com.querydsl.apt.DefaultConfiguration; + +import org.springframework.data.elasticsearch.annotations.Document; + +/** + * Annotation processor to create Querydsl query types for QueryDsl annotated classes. + * + * @author Kevin Leturc + */ +@SupportedAnnotationTypes({ "com.querydsl.core.annotations.*", "org.springframework.data.elasticsearch.annotations.*" }) +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public class ElasticsearchAnnotationProcessor extends AbstractQuerydslProcessor { + + /* + * (non-Javadoc) + * @see com.querydsl.core.apt.AbstractQuerydslProcessor#createConfiguration(javax.annotation.processing.RoundEnvironment) + */ + @Override + protected Configuration createConfiguration(RoundEnvironment roundEnv) { + + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Running " + getClass().getSimpleName()); + + DefaultConfiguration configuration = new DefaultConfiguration(roundEnv, processingEnv.getOptions(), + Collections. emptySet(), QueryEntities.class, Document.class, QuerySupertype.class, + QueryEmbeddable.class, QueryEmbedded.class, QueryTransient.class); + configuration.setUnknownAsEmbedded(true); + + return configuration; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java index 9e1263022..545384897 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactory.java @@ -41,6 +41,7 @@ * @author Rizwan Idrees * @author Mohsin Husen * @author Ryan Henszey + * @author Kevin Leturc */ public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport { @@ -68,9 +69,8 @@ protected Object getTargetRepository(RepositoryInformation metadata) { @Override protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { if (isQueryDslRepository(metadata.getRepositoryInterface())) { - throw new IllegalArgumentException("QueryDsl Support has not been implemented yet."); - } - if (Integer.class.isAssignableFrom(metadata.getIdType()) + return QueryDslElasticsearchRepository.class; + } else if (Integer.class.isAssignableFrom(metadata.getIdType()) || Long.class.isAssignableFrom(metadata.getIdType()) || Double.class.isAssignableFrom(metadata.getIdType())) { return NumberKeyedRepository.class; diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryDslElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryDslElasticsearchRepository.java new file mode 100644 index 000000000..c6b62ff4f --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/QueryDslElasticsearchRepository.java @@ -0,0 +1,230 @@ +/* + * Copyright 2014 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.elasticsearch.repository.support; + +import java.io.Serializable; + +import com.querydsl.elasticsearch.ElasticsearchQuery; +import com.querydsl.core.types.EntityPath; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.PathBuilder; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.querydsl.EntityPathResolver; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; +import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.util.Assert; + +/** + * Special QueryDsl based repository implementation that allows execution {@link Predicate}s in various forms. + * + * @author Kevin Leturc + */ +public class QueryDslElasticsearchRepository extends AbstractElasticsearchRepository + implements QueryDslPredicateExecutor { + + private final PathBuilder builder; + + /** + * Creates a new {@link QueryDslElasticsearchRepository} for the given {@link ElasticsearchEntityInformation} and {@link ElasticsearchOperations}. Uses + * the {@link org.springframework.data.querydsl.SimpleEntityPathResolver} to create an {@link com.querydsl.core.types.EntityPath} for the given domain class. + * + * @param entityInformation The elasticsearch entity information. + * @param elasticsearchOperations The elasticsearch operations. + */ + public QueryDslElasticsearchRepository(ElasticsearchEntityInformation entityInformation, ElasticsearchOperations elasticsearchOperations) { + this(entityInformation, elasticsearchOperations, SimpleEntityPathResolver.INSTANCE); + } + + /** + * Creates a new {@link QueryDslElasticsearchRepository} for the given {@link ElasticsearchEntityInformation}, {@link ElasticsearchOperations} + * and {@link org.springframework.data.querydsl.EntityPathResolver}. + * + * @param entityInformation The elasticsearch entity information. + * @param elasticsearchOperations The elasticsearch operations. + * @param resolver The query dsl path resolver. + */ + public QueryDslElasticsearchRepository(ElasticsearchEntityInformation entityInformation, ElasticsearchOperations elasticsearchOperations, + EntityPathResolver resolver) { + + super(entityInformation, elasticsearchOperations); + Assert.notNull(resolver); + EntityPath path = resolver.createPath(entityInformation.getJavaType()); + this.builder = new PathBuilder(path.getType(), path.getMetadata()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findOne(com.querydsl.core.types.Predicate) + */ + @Override + public T findOne(Predicate predicate) { + return createQueryFor(predicate).fetchFirst(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate) + */ + @Override + public Iterable findAll(Predicate predicate) { + return createQueryFor(predicate).fetch(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Predicate predicate, Sort sort) { + return applySorting(createQueryFor(predicate), sort).fetch(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, com.querydsl.core.types.OrderSpecifier[]) + */ + @Override + public Iterable findAll(Predicate predicate, OrderSpecifier... orders) { + return createQueryFor(predicate).orderBy(orders).fetch(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.OrderSpecifier[]) + */ + @Override + public Iterable findAll(OrderSpecifier... orders) { + return createQuery().orderBy(orders).fetch(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable) + */ + @Override + public Page findAll(Predicate predicate, Pageable pageable) { + ElasticsearchQuery countQuery = createQueryFor(predicate); + ElasticsearchQuery query = createQueryFor(predicate); + + return new PageImpl(applyPagination(query, pageable).fetch(), pageable, countQuery.fetchCount()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#count(com.querydsl.core.types.Predicate) + */ + @Override + public long count(Predicate predicate) { + return createQueryFor(predicate).fetchCount(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#exists(com.querydsl.core.types.Predicate) + */ + @Override + public boolean exists(Predicate predicate) { + return createQueryFor(predicate).fetchCount() > 0; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#stringIdRepresentation(ID) + */ + @Override + protected String stringIdRepresentation(ID id) { + // Handle String and Number ids + return String.valueOf(id); + } + + /** + * Creates an {@link ElasticsearchQuery} for the given {@link Predicate}. + * + * @param predicate The predicate. + * @return The querydsl query. + */ + private ElasticsearchQuery createQueryFor(Predicate predicate) { + return createQuery().where(predicate); + } + + /** + * Creates an {@link ElasticsearchQuery}. + *ate. + * @return The querydsl query. + */ + private ElasticsearchQuery createQuery() { + return new SpringDataElasticsearchQuery(elasticsearchOperations, entityInformation); + } + + /** + * Applies the given {@link Pageable} to the given {@link ElasticsearchQuery}. + * + * @param query The query to apply pagination. + * @param pageable The pageable to apply. + * @return The querydsl query. + */ + private ElasticsearchQuery applyPagination(ElasticsearchQuery query, Pageable pageable) { + + if (pageable == null) { + return query; + } + + query = query.offset(pageable.getOffset()).limit(pageable.getPageSize()); + return applySorting(query, pageable.getSort()); + } + + /** + * Applies the given {@link Sort} to the given {@link ElasticsearchQuery}. + * + * @param query The query to apply sort. + * @param sort The sort to appy. + * @return The querydsl query. + */ + private ElasticsearchQuery applySorting(ElasticsearchQuery query, Sort sort) { + + if (sort == null) { + return query; + } + + for (Sort.Order order : sort) { + query.orderBy(toOrder(order)); + } + + return query; + } + + /** + * Transforms a plain {@link Sort.Order} into a QueryDsl specific {@link OrderSpecifier}. + * + * @param order The order to convert. + * @return The querydsl order. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private OrderSpecifier toOrder(Sort.Order order) { + + Expression property = builder.get(order.getProperty()); + + return new OrderSpecifier(order.isAscending() ? com.querydsl.core.types.Order.ASC + : com.querydsl.core.types.Order.DESC, property); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/SpringDataElasticsearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/SpringDataElasticsearchQuery.java new file mode 100644 index 000000000..e0b5b5712 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/SpringDataElasticsearchQuery.java @@ -0,0 +1,131 @@ +/* + * Copyright 2014 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.elasticsearch.repository.support; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Method; + +import com.google.common.base.Function; +import com.querydsl.elasticsearch.ElasticsearchQuery; +import com.querydsl.core.types.Predicate; + +import org.elasticsearch.search.SearchHit; +import org.springframework.data.elasticsearch.ElasticsearchException; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.EntityMapper; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.MappingContext; + +/** + * Spring Data specific {@link ElasticsearchQuery} implementations. + * + * @author Kevin Leturc + */ +public class SpringDataElasticsearchQuery extends ElasticsearchQuery { + + private final String index; + private final String type; + + /** + * Creates a new {@link SpringDataElasticsearchQuery}. + * + * @param operations must not be {@literal null}. + * @param information must not be {@literal null}. + */ + public SpringDataElasticsearchQuery(final ElasticsearchOperations operations, + final ElasticsearchEntityInformation information) { + this(operations, information.getJavaType(), information.getIndexName(), information.getType()); + } + + /** + * Creates a new {@link SpringDataElasticsearchQuery}. + * + * @param operations must not be {@literal null}. + * @param entityType must not be {@literal null}. + * @param index must not be {@literal null}. + * @param type must not be {@literal null}. + */ + public SpringDataElasticsearchQuery(final ElasticsearchOperations operations, + final Class entityType, + final String index, + final String type) { + super(operations.getClient(), + new SpringDataElasticsearchTransformer(operations, entityType), + new SpringDataElasticsearchSerializer(operations.getElasticsearchConverter())); + this.index = index; + this.type = type; + } + + @Override + public String getIndex() { + return index; + } + + @Override + public String getType() { + return type; + } + + @Override + public SpringDataElasticsearchQuery where(Predicate... o) { + // Override this method to allow overriding list() to avoid to count twice the results on Elasticsearch + super.where(o); + return this; + } + + private static class SpringDataElasticsearchTransformer implements Function { + + private final EntityMapper entityMapper; + private final MappingContext, ElasticsearchPersistentProperty> mappingContext; + private final Class entityType; + + public SpringDataElasticsearchTransformer(ElasticsearchOperations operations, Class entityType) { + this.entityMapper = operations.getResultsMapper().getEntityMapper(); + this.mappingContext = operations.getElasticsearchConverter().getMappingContext(); + this.entityType = entityType; + } + public T apply(SearchHit input) { + try { + T entity = entityMapper.mapToObject(input.getSourceAsString(), entityType); + setPersistentEntityId(entity, input.getId(), entityType); + return entity; + } catch (IOException e) { + throw new ElasticsearchException("failed to map source [ " + input + "] to class " + entityType.getSimpleName(), e); + } + } + + private void setPersistentEntityId(T result, String id, Class clazz) { + if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) { + PersistentProperty idProperty = mappingContext.getPersistentEntity(clazz).getIdProperty(); + // Only deal with String because ES generated Ids are strings ! + if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { + Method setter = idProperty.getSetter(); + if (setter != null) { + try { + setter.invoke(result, id); + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + } + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/SpringDataElasticsearchSerializer.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/SpringDataElasticsearchSerializer.java new file mode 100644 index 000000000..86a1671ea --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/SpringDataElasticsearchSerializer.java @@ -0,0 +1,67 @@ +/* + * Copyright 2014 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.elasticsearch.repository.support; + +import com.querydsl.elasticsearch.ElasticsearchSerializer; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.PathType; + +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.util.Assert; + +/** + * Custom {@link ElasticsearchSerializer} to take mapping information into account when building keys for constraints. + * + * @author Kevin Leturc + */ +public class SpringDataElasticsearchSerializer extends ElasticsearchSerializer { + + private final MappingContext, ElasticsearchPersistentProperty> mappingContext; + + /** + * Creates a new {@link SpringDataElasticsearchSerializer} for the given {@link ElasticsearchConverter}. + * + * @param converter must not be {@literal null}. + */ + public SpringDataElasticsearchSerializer(ElasticsearchConverter converter) { + + Assert.notNull(converter, "ElasticsearchConverter must not be null!"); + + this.mappingContext = converter.getMappingContext(); + } + + /** + * {@inheritDoc} + */ + @Override + protected String getKeyForPath(Path expr, PathMetadata metadata) { + + if (!metadata.getPathType().equals(PathType.PROPERTY)) { + return super.getKeyForPath(expr, metadata); + } + + Path parent = metadata.getParent(); + ElasticsearchPersistentEntity entity = mappingContext.getPersistentEntity(parent.getType()); + ElasticsearchPersistentProperty property = entity.getPersistentProperty(metadata.getName()); + + return property == null ? super.getKeyForPath(expr, metadata) : property.getFieldName(); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/querydsl/QueryDslElasticsearchRepository.java b/src/test/java/org/springframework/data/elasticsearch/repositories/querydsl/QueryDslElasticsearchRepository.java new file mode 100644 index 000000000..72c534490 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/querydsl/QueryDslElasticsearchRepository.java @@ -0,0 +1,30 @@ +/* + * 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.elasticsearch.repositories.querydsl; + +import java.util.List; + +import org.springframework.data.elasticsearch.entities.SampleEntity; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; + +/** + * @author Kevin Leturc + */ +public interface QueryDslElasticsearchRepository + extends ElasticsearchRepository, QueryDslPredicateExecutor { + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/QueryDslElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/QueryDslElasticsearchRepositoryTests.java new file mode 100644 index 000000000..5689f2b08 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/QueryDslElasticsearchRepositoryTests.java @@ -0,0 +1,283 @@ +/* + * Copyright 2013-2014 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.elasticsearch.repository.support; + +import java.util.Arrays; +import java.util.List; + +import com.querydsl.core.types.dsl.BooleanExpression; + +import org.elasticsearch.common.collect.Lists; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.SearchQuery; +import org.springframework.data.elasticsearch.entities.QSampleEntity; +import org.springframework.data.elasticsearch.entities.SampleEntity; +import org.springframework.data.elasticsearch.repositories.querydsl.QueryDslElasticsearchRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.apache.commons.lang.RandomStringUtils.randomNumeric; +import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; +import static org.elasticsearch.index.query.QueryBuilders.termQuery; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * @author Kevin Leturc + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:/querydsl-repository-test.xml") +public class QueryDslElasticsearchRepositoryTests { + + @Autowired + private QueryDslElasticsearchRepository repository; + + @Autowired + private ElasticsearchTemplate elasticsearchTemplate; + + + @Before + public void before() { + elasticsearchTemplate.deleteIndex(SampleEntity.class); + elasticsearchTemplate.createIndex(SampleEntity.class); + elasticsearchTemplate.refresh(SampleEntity.class, true); + } + + @Test + public void shouldDoBulkIndexDocument() { + // given + String documentId1 = randomNumeric(5); + SampleEntity sampleEntity1 = new SampleEntity(); + sampleEntity1.setId(documentId1); + sampleEntity1.setMessage("some message"); + sampleEntity1.setVersion(System.currentTimeMillis()); + + String documentId2 = randomNumeric(5); + SampleEntity sampleEntity2 = new SampleEntity(); + sampleEntity2.setId(documentId2); + sampleEntity2.setMessage("some message"); + sampleEntity2.setVersion(System.currentTimeMillis()); + + // when + repository.save(Arrays.asList(sampleEntity1, sampleEntity2)); + // then + SampleEntity entity1FromElasticSearch = repository.findOne(documentId1); + assertThat(entity1FromElasticSearch, is(notNullValue())); + + SampleEntity entity2FromElasticSearch = repository.findOne(documentId2); + assertThat(entity2FromElasticSearch, is(notNullValue())); + } + + @Test + public void shouldSaveDocument() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("some message"); + sampleEntity.setVersion(System.currentTimeMillis()); + // when + repository.save(sampleEntity); + // then + SampleEntity entityFromElasticSearch = repository.findOne(documentId); + assertThat(entityFromElasticSearch, is(notNullValue())); + } + + @Test + public void shouldSaveDocumentWithoutId() { + // given + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setMessage("some message"); + sampleEntity.setVersion(System.currentTimeMillis()); + // when + repository.save(sampleEntity); + // then + assertThat(sampleEntity.getId(), is(notNullValue())); + } + + @Test + public void shouldFindDocumentById() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("some message"); + sampleEntity.setVersion(System.currentTimeMillis()); + repository.save(sampleEntity); + // when + SampleEntity entityFromElasticSearch = repository.findOne(documentId); + // then + assertThat(entityFromElasticSearch, is(notNullValue())); + assertThat(sampleEntity, is((equalTo(sampleEntity)))); + } + + @Test + public void shouldReturnCountOfDocuments() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("some message"); + sampleEntity.setVersion(System.currentTimeMillis()); + repository.save(sampleEntity); + // when + Long count = repository.count(); + // then + assertThat(count, is(greaterThanOrEqualTo(1L))); + } + + @Test + public void shouldFindAllDocuments() { + // when + Iterable results = repository.findAll(); + // then + assertThat(results, is(notNullValue())); + } + + @Test + public void shouldDeleteDocument() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("some message"); + sampleEntity.setVersion(System.currentTimeMillis()); + repository.save(sampleEntity); + // when + repository.delete(documentId); + // then + SampleEntity entityFromElasticSearch = repository.findOne(documentId); + assertThat(entityFromElasticSearch, is(nullValue())); + } + + @Test + public void shouldSearchDocumentsGivenContainsPredicate() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("some test message"); + sampleEntity.setVersion(System.currentTimeMillis()); + repository.save(sampleEntity); + + BooleanExpression predicate = QSampleEntity.sampleEntity.message.contains("test"); + // when + List entities = Lists.newArrayList(repository.findAll(predicate)); + // then + assertThat(entities, is(notNullValue())); + assertThat(entities.size(), is(equalTo(1))); + assertThat(entities.get(0), is(equalTo(sampleEntity))); + } + + @Test + public void shouldSaveIterableEntities() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity1 = new SampleEntity(); + sampleEntity1.setId(documentId); + sampleEntity1.setMessage("hello world."); + sampleEntity1.setVersion(System.currentTimeMillis()); + + String documentId2 = randomNumeric(5); + SampleEntity sampleEntity2 = new SampleEntity(); + sampleEntity2.setId(documentId2); + sampleEntity2.setMessage("hello world."); + sampleEntity2.setVersion(System.currentTimeMillis()); + + Iterable sampleEntities = Arrays.asList(sampleEntity1, sampleEntity2); + // when + repository.save(sampleEntities); + // then + Page entities = repository.search(termQuery("id", documentId), new PageRequest(0, 50)); + assertNotNull(entities); + } + + @Test + public void shouldReturnTrueGivenPredicateExists() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("hello world."); + sampleEntity.setVersion(System.currentTimeMillis()); + repository.save(sampleEntity); + + // when + BooleanExpression predicate = QSampleEntity.sampleEntity.message.eq("hello world"); + boolean exist = repository.exists(predicate); + + // then + assertEquals(exist, true); + } + + @Test + public void shouldDeleteAll() { + // when + repository.deleteAll(); + // then + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + Page sampleEntities = repository.search(searchQuery); + assertThat(sampleEntities.getTotalElements(), equalTo(0L)); + } + + @Test + public void shouldDeleteEntity() { + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("hello world."); + sampleEntity.setVersion(System.currentTimeMillis()); + repository.save(sampleEntity); + // when + repository.delete(sampleEntity); + // then + SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); + Page sampleEntities = repository.search(searchQuery); + assertThat(sampleEntities.getTotalElements(), equalTo(0L)); + } + + @Test + public void shouldSortByGivenField() { + // todo + // given + String documentId = randomNumeric(5); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(documentId); + sampleEntity.setMessage("world"); + repository.save(sampleEntity); + + String documentId2 = randomNumeric(5); + SampleEntity sampleEntity2 = new SampleEntity(); + sampleEntity2.setId(documentId2); + sampleEntity2.setMessage("hello"); + repository.save(sampleEntity2); + // when + List sampleEntities = Lists.newArrayList(repository.findAll(QSampleEntity.sampleEntity.message.asc())); + // then + assertThat(sampleEntities, is(notNullValue())); + assertThat(sampleEntities.get(0).getMessage(), is(equalTo("hello"))); + assertThat(sampleEntities.get(1).getMessage(), is(equalTo("world"))); + } + +} diff --git a/src/test/resources/querydsl-repository-test.xml b/src/test/resources/querydsl-repository-test.xml new file mode 100644 index 000000000..59717827c --- /dev/null +++ b/src/test/resources/querydsl-repository-test.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/template.mf b/template.mf index 70dcc48b8..bf4e713b1 100644 --- a/template.mf +++ b/template.mf @@ -7,7 +7,11 @@ Bundle-RequiredExecutionEnvironment: J2SE-1.5 Export-Template: org.springframework.data.elasticsearch.*;version="${project.version}" Import-Template: + com.google.common.base.*;version="[11.0.0,14.0.0)";resolution:=optional, + com.querydsl.*;version="${querydsl:[=.=.=,+1.0.0)}";resolution:=optional, + javax.annotation.processing.*;version="0", javax.enterprise.*;version="${cdi:[=.=.=,+1.0.0)}";resolution:=optional, + javax.tools.*;version="0", org.apache.commons.collections.*;version="${commonscollections:[=.=.=,+1.0.0)}";resolution:=optional, org.apache.commons.lang.*;version="${commonslang:[=.=.=,+1.0.0)}", com.fasterxml.jackson.*;version="${jackson:[=.=.=,+1.0.0)}";resolution:=optional,