/*
* Copyright 2012-2017 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.solr.repository.query;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.solr.common.params.HighlightParams;
import org.hamcrest.collection.IsEmptyIterable;
import org.hamcrest.core.IsEqual;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.solr.core.SolrCallback;
import org.springframework.data.solr.core.SolrOperations;
import org.springframework.data.solr.core.convert.MappingSolrConverter;
import org.springframework.data.solr.core.convert.SolrConverter;
import org.springframework.data.solr.core.mapping.SimpleSolrMappingContext;
import org.springframework.data.solr.core.mapping.SolrPersistentEntity;
import org.springframework.data.solr.core.query.Field;
import org.springframework.data.solr.core.query.HighlightOptions;
import org.springframework.data.solr.core.query.HighlightQuery;
import org.springframework.data.solr.core.query.Query;
import org.springframework.data.solr.core.query.SimpleField;
import org.springframework.data.solr.core.query.SimpleQuery;
import org.springframework.data.solr.core.query.SimpleStringCriteria;
import org.springframework.data.solr.core.query.StatsOptions;
import org.springframework.data.solr.repository.Facet;
import org.springframework.data.solr.repository.Highlight;
import org.springframework.data.solr.repository.ProductBean;
import org.springframework.data.solr.repository.SelectiveStats;
import org.springframework.data.solr.repository.SolrCrudRepository;
import org.springframework.data.solr.repository.Stats;
import org.springframework.data.solr.repository.support.MappingSolrEntityInformation;
/**
* @author Christoph Strobl
* @author Francisco Spaeth
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.Silent.class)
public class SolrQueryTests {
private @Mock SolrOperations solrOperationsMock;
private @Mock SolrPersistentEntity<ProductBean> persitentEntityMock;
private SolrEntityInformationCreator entityInformationCreator;
private RepositoryMetadata metadataMock = AbstractRepositoryMetadata.getMetadata(Repo1.class);
private SimpleSolrMappingContext mappingContext;
private SolrConverter solrConverter;
@SuppressWarnings("unchecked")
@Before
public void setUp() {
mappingContext = new SimpleSolrMappingContext();
solrConverter = new MappingSolrConverter(mappingContext);
entityInformationCreator = new SolrEntityInformationCreatorImpl();
Mockito.when(persitentEntityMock.getType()).thenReturn(ProductBean.class);
Mockito.when(solrOperationsMock.execute(Mockito.any(SolrCallback.class)))
.thenReturn(new PageImpl<>(Collections.<ProductBean> emptyList()));
Mockito.when(solrOperationsMock.getConverter()).thenReturn(solrConverter);
}
@Test
public void testQueryWithHighlightAndFaceting() throws NoSuchMethodException, SecurityException {
createQueryForMethod("findAndApplyHighlightingAndFaceting", Pageable.class)
.execute(new Object[] { new PageRequest(0, 10) });
}
@SuppressWarnings("unchecked")
@Test
public void testQueryWithHighlight() {
ArgumentCaptor<HighlightQuery> captor = ArgumentCaptor.forClass(HighlightQuery.class);
createQueryForMethod("findAndApplyHighlighting", Pageable.class).execute(new Object[] { new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForHighlightPage(Mockito.eq("collection-1"),
captor.capture(), (Class<ProductBean>) Mockito.any());
HighlightOptions capturedOptions = captor.getValue().getHighlightOptions();
Assert.assertNotNull(capturedOptions);
}
@SuppressWarnings("unchecked")
@Test
public void testQueryWithHighlightParameters() {
ArgumentCaptor<HighlightQuery> captor = ArgumentCaptor.forClass(HighlightQuery.class);
createQueryForMethod("findAndApplyHighlightingAllParameters", Pageable.class)
.execute(new Object[] { new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForHighlightPage(Mockito.eq("collection-1"),
captor.capture(), (Class<ProductBean>) Mockito.any());
HighlightOptions capturedOptions = captor.getValue().getHighlightOptions();
Assert.assertNotNull(capturedOptions);
Assert.assertEquals("<b>", capturedOptions.getSimplePrefix());
Assert.assertEquals("</b>", capturedOptions.getSimplePostfix());
Assert.assertEquals("name", capturedOptions.getFields().get(0).getName());
Assert.assertEquals("description", capturedOptions.getFields().get(1).getName());
Assert.assertEquals("simple", capturedOptions.getFormatter());
Assert.assertEquals(Integer.valueOf(10), capturedOptions.getFragsize());
Assert.assertEquals(Integer.valueOf(20), capturedOptions.getNrSnipplets());
Assert.assertEquals("name:with",
((SimpleStringCriteria) capturedOptions.getQuery().getCriteria()).getQueryString());
}
@SuppressWarnings("unchecked")
@Test
public void testQueryWithParametrizedHighlightQuery() {
ArgumentCaptor<HighlightQuery> captor = ArgumentCaptor.forClass(HighlightQuery.class);
createQueryForMethod("findAndApplyHighlightingWithParametrizedHighlightQuery", String.class, Pageable.class)
.execute(new Object[] { "spring", new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForHighlightPage(Mockito.eq("collection-1"),
captor.capture(), (Class<ProductBean>) Mockito.any());
HighlightOptions capturedOptions = captor.getValue().getHighlightOptions();
Assert.assertEquals("name:*spring*",
((SimpleStringCriteria) capturedOptions.getQuery().getCriteria()).getQueryString());
}
@SuppressWarnings("unchecked")
@Test
public void testQueryWithNonDefaultHighlightFormatter() {
ArgumentCaptor<HighlightQuery> captor = ArgumentCaptor.forClass(HighlightQuery.class);
createQueryForMethod("findAndApplyHighlightingWithNonDefaultFormatter", Pageable.class)
.execute(new Object[] { new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForHighlightPage(Mockito.eq("collection-1"),
captor.capture(), (Class<ProductBean>) Mockito.any());
HighlightOptions capturedOptions = captor.getValue().getHighlightOptions();
Assert.assertNotNull(capturedOptions);
Assert.assertNull(capturedOptions.getSimplePrefix());
Assert.assertNull(capturedOptions.getSimplePrefix());
Assert.assertNull(capturedOptions.getSimplePostfix());
Assert.assertEquals("postingshighlighter", capturedOptions.getFormatter());
Assert.assertEquals("{pre}", capturedOptions.getHighlightParameterValue(HighlightParams.TAG_PRE));
Assert.assertEquals("{post}", capturedOptions.getHighlightParameterValue(HighlightParams.TAG_POST));
}
@Test // DATASOLR-170
public void shouldApplyLimitCorrectlyWhenPageSizeToBig() throws NoSuchMethodException, SecurityException {
Method method = Repo1.class.getMethod("findTop5ByName", String.class, Pageable.class);
SolrQueryMethod sqm = createSolrQueryMethodFrom(method);
PartTreeSolrQuery ptsq = new PartTreeSolrQuery("collection-1", sqm, this.solrOperationsMock);
ptsq.execute(new Object[] { "foo", new PageRequest(0, 10) });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForPage(Mockito.eq("collection-1"), captor.capture(),
(Class<?>) Mockito.any());
Assert.assertThat(captor.getValue().getPageRequest().getPageNumber(), IsEqual.equalTo(0));
Assert.assertThat(captor.getValue().getPageRequest().getPageSize(), IsEqual.equalTo(5));
}
@Test // DATASOLR-170
public void shouldApplyLimitCorrectlyToPageWhenPageInsideLimit() throws NoSuchMethodException, SecurityException {
Method method = Repo1.class.getMethod("findTop5ByName", String.class, Pageable.class);
SolrQueryMethod sqm = createSolrQueryMethodFrom(method);
PartTreeSolrQuery ptsq = new PartTreeSolrQuery("collection-1", sqm, this.solrOperationsMock);
ptsq.execute(new Object[] { "foo", new PageRequest(1, 2) });
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForPage(Mockito.eq("collection-1"), captor.capture(),
(Class<?>) Mockito.any());
Assert.assertThat(captor.getValue().getPageRequest().getPageNumber(), IsEqual.equalTo(1));
Assert.assertThat(captor.getValue().getPageRequest().getPageSize(), IsEqual.equalTo(2));
}
@Test // DATASOLR-170
public void shouldNotCallServerIfPageOutsideLimit() throws NoSuchMethodException, SecurityException {
Method method = Repo1.class.getMethod("findTop5ByName", String.class, Pageable.class);
SolrQueryMethod sqm = createSolrQueryMethodFrom(method);
PartTreeSolrQuery ptsq = new PartTreeSolrQuery(sqm, this.solrOperationsMock);
ptsq.execute(new Object[] { "foo", new PageRequest(2, 5) });
Mockito.verify(solrOperationsMock, Mockito.never()).queryForPage(Mockito.eq("collection-1"),
Mockito.any(Query.class), (Class<?>) Mockito.any());
}
@Test // DATASOLR-186
public void sliceShouldTriggerPagedExecution() {
createQueryForMethod("findByName", String.class, Pageable.class)
.execute(new Object[] { "sliceme", new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForPage(Mockito.eq("collection-1"),
Mockito.any(Query.class), Mockito.<Class<ProductBean>> any());
}
@SuppressWarnings("unchecked")
@Test // DATASOLR-160
public void testQueryWithStats() {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
createQueryForMethod("findAndApplyStats", Pageable.class).execute(new Object[] { new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForPage(Mockito.eq("collection-1"), captor.capture(),
(Class<ProductBean>) Mockito.any());
StatsOptions capturedOptions = captor.getValue().getStatsOptions();
Assert.assertEquals(2, capturedOptions.getFields().size());
Assert.assertTrue(
capturedOptions.getFields().containsAll(Arrays.asList(new SimpleField("field1"), new SimpleField("field4"))));
Assert.assertEquals(2, capturedOptions.getFacets().size());
Assert.assertTrue(
capturedOptions.getFacets().containsAll(Arrays.asList(new SimpleField("field2"), new SimpleField("field3"))));
Collection<Field> selectiveFacetsField = capturedOptions.getSelectiveFacets().get(new SimpleField("field4"));
List<SimpleField> selectiveFacetsFields = Arrays.asList(new SimpleField("field4_1"), new SimpleField("field4_2"));
Assert.assertEquals(1, capturedOptions.getSelectiveFacets().size());
Assert.assertTrue(selectiveFacetsField.containsAll(selectiveFacetsFields));
}
@SuppressWarnings("unchecked")
@Test // DATASOLR-160
public void testQueryWithStatsNonSelective() {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
createQueryForMethod("findAndApplyStatsNonSelective", Pageable.class)
.execute(new Object[] { new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForPage(Mockito.eq("collection-1"), captor.capture(),
(Class<ProductBean>) Mockito.any());
StatsOptions capturedOptions = captor.getValue().getStatsOptions();
Assert.assertEquals(1, capturedOptions.getFields().size());
Assert.assertTrue(capturedOptions.getFields().containsAll(Collections.singletonList(new SimpleField("field1"))));
Assert.assertEquals(2, capturedOptions.getFacets().size());
Assert.assertTrue(
capturedOptions.getFacets().containsAll(Arrays.asList(new SimpleField("field2"), new SimpleField("field3"))));
Assert.assertThat(capturedOptions.getSelectiveFacets().entrySet(), IsEmptyIterable.emptyIterable());
}
@SuppressWarnings("unchecked")
@Test // DATASOLR-160
public void testQueryWithStatsNoFacets() {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
createQueryForMethod("findAndApplyStatsNoFacets", Pageable.class).execute(new Object[] { new PageRequest(0, 10) });
Mockito.verify(solrOperationsMock, Mockito.times(1)).queryForPage(Mockito.eq("collection-1"), captor.capture(),
(Class<ProductBean>) Mockito.any());
StatsOptions capturedOptions = captor.getValue().getStatsOptions();
Assert.assertEquals(1, capturedOptions.getFields().size());
Assert.assertTrue(capturedOptions.getFields().containsAll(Collections.singletonList(new SimpleField("field1"))));
Assert.assertThat(capturedOptions.getFacets(), IsEmptyIterable.emptyIterable());
Assert.assertThat(capturedOptions.getSelectiveFacets().entrySet(), IsEmptyIterable.emptyIterable());
}
private RepositoryQuery createQueryForMethod(String methodName, Class<?>... paramTypes) {
try {
return this.createQueryForMethod(Repo1.class.getMethod(methodName, paramTypes));
} catch (NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
private RepositoryQuery createQueryForMethod(Method method) {
return new SolrQueryImpl(this.solrOperationsMock, createSolrQueryMethodFrom(method));
}
private SolrQueryMethod createSolrQueryMethodFrom(Method method) {
return new SolrQueryMethod(method, metadataMock, new SpelAwareProxyProjectionFactory(), entityInformationCreator);
}
private interface Repo1 extends SolrCrudRepository<ProductBean, String> {
@Facet(fields = { "name" })
@Highlight
Page<ProductBean> findAndApplyHighlightingAndFaceting(Pageable page);
@Highlight
Page<ProductBean> findAndApplyHighlighting(Pageable page);
@Highlight(fields = { "name", "description" }, fragsize = 10, snipplets = 20, prefix = "<b>", postfix = "</b>",
query = "name:with", formatter = "simple")
Page<ProductBean> findAndApplyHighlightingAllParameters(Pageable page);
@Highlight(query = "name:*?0*")
Page<ProductBean> findAndApplyHighlightingWithParametrizedHighlightQuery(String name, Pageable page);
@Highlight(formatter = "postingshighlighter", prefix = "{pre}", postfix = "{post}")
Page<ProductBean> findAndApplyHighlightingWithNonDefaultFormatter(Pageable page);
Page<ProductBean> findTop5ByName(String name, Pageable page);
Slice<ProductBean> findByName(String name, Pageable page);
@Stats(value = "field1", facets = { "field2", "field3" }, //
selective = @SelectiveStats(field = "field4", facets = { "field4_1", "field4_2" }))
Page<ProductBean> findAndApplyStats(Pageable page);
@Stats(value = "field1", facets = { "field2", "field3" })
Page<ProductBean> findAndApplyStatsNonSelective(Pageable page);
@Stats(value = "field1")
Page<ProductBean> findAndApplyStatsNoFacets(Pageable page);
}
private class SolrEntityInformationCreatorImpl implements SolrEntityInformationCreator {
@SuppressWarnings("unchecked")
@Override
public <T, ID> SolrEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
return (SolrEntityInformation<T, ID>) new SolrEntityInformationImpl(persitentEntityMock);
}
}
private class SolrEntityInformationImpl extends MappingSolrEntityInformation<ProductBean, String> {
public SolrEntityInformationImpl(SolrPersistentEntity<ProductBean> entity) {
super(entity);
}
@Override
public Class<String> getIdType() {
return String.class;
}
@Override
public Class<ProductBean> getJavaType() {
return ProductBean.class;
}
}
private class SolrQueryImpl extends AbstractSolrQuery {
public SolrQueryImpl(SolrOperations solrOperations, SolrQueryMethod solrQueryMethod) {
super("collection-1", solrOperations, solrQueryMethod);
}
@Override
protected Query createQuery(SolrParameterAccessor parameterAccessor) {
return new SimpleQuery(new SimpleStringCriteria("fake:query"));
}
}
}