Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HSEARCH-2387 Add Elasticsearch dynamic mapping type tests
- Loading branch information
Showing
3 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
...search/src/test/java/org/hibernate/search/elasticsearch/test/bridge/DynamicMappingIT.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
* Hibernate Search, full-text search for your domain model | ||
* | ||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later | ||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. | ||
*/ | ||
package org.hibernate.search.elasticsearch.test.bridge; | ||
|
||
import static org.fest.assertions.Assertions.assertThat; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import org.apache.lucene.search.Query; | ||
import org.hibernate.search.backend.spi.Work; | ||
import org.hibernate.search.backend.spi.WorkType; | ||
import org.hibernate.search.cfg.Environment; | ||
import org.hibernate.search.elasticsearch.cfg.ElasticsearchEnvironment; | ||
import org.hibernate.search.elasticsearch.schema.impl.model.DynamicType; | ||
import org.hibernate.search.engine.integration.impl.ExtendedSearchIntegrator; | ||
import org.hibernate.search.exception.ErrorContext; | ||
import org.hibernate.search.exception.ErrorHandler; | ||
import org.hibernate.search.exception.SearchException; | ||
import org.hibernate.search.query.dsl.QueryBuilder; | ||
import org.hibernate.search.query.engine.spi.EntityInfo; | ||
import org.hibernate.search.testsupport.junit.SearchFactoryHolder; | ||
import org.hibernate.search.testsupport.setup.TransactionContextForTest; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
|
||
/** | ||
* Checks that there is a way to create fields in an Elasticsearch indexes having dynamic attributes even when the | ||
* global option is set to strict. | ||
* | ||
* @author Davide D'Alto | ||
*/ | ||
public class DynamicMappingIT { | ||
|
||
private static final String DYNAMIC_MAPPING = "hibernate.search." + ElasticsearchDynamicIndexedValueHolder.INDEX_NAME + "." | ||
+ ElasticsearchEnvironment.DYNAMIC_MAPPING; | ||
|
||
@Rule | ||
public SearchFactoryHolder sfHolder = new SearchFactoryHolder( ElasticsearchDynamicIndexedValueHolder.class ) | ||
.withProperty( Environment.ERROR_HANDLER, TestExceptionHandler.class.getName() ) | ||
.withProperty( DYNAMIC_MAPPING, DynamicType.STRICT.name() ); | ||
|
||
@Test | ||
public void testIndexingWithDynamicField() { | ||
// Store some not defined data: | ||
ElasticsearchDynamicIndexedValueHolder holder = new ElasticsearchDynamicIndexedValueHolder( "1" ) | ||
.dynamicProperty( "age", "227" ) | ||
.dynamicProperty( "name", "Thorin" ) | ||
.dynamicProperty( "surname", "Oakenshield" ) | ||
.dynamicProperty( "race", "dwarf" ); | ||
|
||
index( holder ); | ||
List<EntityInfo> fieldValue = searchField( "dynamicField.name" ); | ||
|
||
assertThat( fieldValue ).hasSize( 1 ); | ||
assertThat( fieldValue.get( 0 ).getProjection()[0] ).isEqualTo( "Thorin" ); | ||
} | ||
|
||
@Test | ||
public void testIndexingWithStrictField() { | ||
// This property should be mapped with dynamic: strict, this means that we cannot change the value adding new | ||
// properties and therefore an error should occurs | ||
ElasticsearchDynamicIndexedValueHolder holder = new ElasticsearchDynamicIndexedValueHolder( "2" ) | ||
.strictProperty( "age", "64" ) | ||
.strictProperty( "name", "Gimli" ) | ||
.strictProperty( "race", "dwarf" ); | ||
|
||
index( holder ); | ||
|
||
TestExceptionHandler errorHandler = getErrorHandler(); | ||
|
||
assertThat( errorHandler.getHandleInvocations() ).hasSize( 1 ); | ||
|
||
ErrorContext errorContext = errorHandler.getHandleInvocations().get( 0 ); | ||
Throwable throwable = errorContext.getThrowable(); | ||
|
||
assertThat( throwable ).isInstanceOf( SearchException.class ); | ||
assertThat( throwable.getMessage() ).startsWith( "HSEARCH400007" ); | ||
assertThat( throwable.getMessage() ).contains( "strict_dynamic_mapping_exception" ); | ||
assertThat( throwable.getMessage() ).contains( "strictField" ); | ||
} | ||
|
||
private TestExceptionHandler getErrorHandler() { | ||
ExtendedSearchIntegrator searchFactory = sfHolder.getSearchFactory(); | ||
ErrorHandler errorHandler = searchFactory.getErrorHandler(); | ||
assertThat( errorHandler ).isInstanceOf( TestExceptionHandler.class ); | ||
return (TestExceptionHandler) errorHandler; | ||
} | ||
|
||
private void index(ElasticsearchDynamicIndexedValueHolder holder) { | ||
ExtendedSearchIntegrator searchFactory = sfHolder.getSearchFactory(); | ||
Work work = new Work( holder, holder.id, WorkType.ADD, false ); | ||
TransactionContextForTest tc = new TransactionContextForTest(); | ||
searchFactory.getWorker().performWork( work, tc ); | ||
tc.end(); | ||
} | ||
|
||
private List<EntityInfo> searchField(String field) { | ||
ExtendedSearchIntegrator searchFactory = sfHolder.getSearchFactory(); | ||
QueryBuilder guestQueryBuilder = searchFactory.buildQueryBuilder().forEntity( ElasticsearchDynamicIndexedValueHolder.class ).get(); | ||
Query queryAllGuests = guestQueryBuilder.all().createQuery(); | ||
|
||
return searchFactory.createHSQuery( queryAllGuests, ElasticsearchDynamicIndexedValueHolder.class ) | ||
.projection( field ) | ||
.queryEntityInfos(); | ||
} | ||
|
||
public static class TestExceptionHandler implements ErrorHandler { | ||
|
||
private List<ErrorContext> handleInvocations = new ArrayList<>(); | ||
|
||
@Override | ||
public void handle(ErrorContext context) { | ||
handleInvocations.add( context ); | ||
} | ||
|
||
@Override | ||
public void handleException(String errorMsg, Throwable exception) { | ||
} | ||
|
||
public List<ErrorContext> getHandleInvocations() { | ||
return handleInvocations; | ||
} | ||
|
||
public void reset() { | ||
handleInvocations.clear(); | ||
} | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
...rg/hibernate/search/elasticsearch/test/bridge/ElasticsearchDynamicIndexedValueHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Hibernate Search, full-text search for your domain model | ||
* | ||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later | ||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. | ||
*/ | ||
package org.hibernate.search.elasticsearch.test.bridge; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import org.hibernate.search.annotations.Analyze; | ||
import org.hibernate.search.annotations.DocumentId; | ||
import org.hibernate.search.annotations.Field; | ||
import org.hibernate.search.annotations.FieldBridge; | ||
import org.hibernate.search.annotations.Indexed; | ||
import org.hibernate.search.annotations.Norms; | ||
import org.hibernate.search.annotations.Parameter; | ||
import org.hibernate.search.annotations.Store; | ||
|
||
/** | ||
* @author Davide D'Alto | ||
*/ | ||
@Indexed(index = ElasticsearchDynamicIndexedValueHolder.INDEX_NAME) | ||
public class ElasticsearchDynamicIndexedValueHolder { | ||
|
||
public static final String INDEX_NAME = "elasticsearchDynamicIndex"; | ||
|
||
@DocumentId | ||
public final String id; | ||
|
||
@Field(norms = Norms.NO, store = Store.YES, analyze = Analyze.YES, name = "dynamicField") | ||
@FieldBridge( | ||
impl = MapAsInnerObjectFieldBridge.class, | ||
params = @Parameter(name = MapAsInnerObjectFieldBridge.DYNAMIC, value = "true") | ||
) | ||
private Map<String, String> dynamicFields = new HashMap<String, String>(); | ||
|
||
@Field(norms = Norms.NO, store = Store.YES, analyze = Analyze.YES, name = "strictField") | ||
@FieldBridge(impl = MapAsInnerObjectFieldBridge.class) | ||
private Map<String, String> strictFields = new HashMap<String, String>(); | ||
|
||
public ElasticsearchDynamicIndexedValueHolder(String id) { | ||
this.id = id; | ||
} | ||
|
||
public ElasticsearchDynamicIndexedValueHolder dynamicProperty(String key, String value) { | ||
dynamicFields.put( key, value ); | ||
return this; | ||
} | ||
|
||
/* | ||
* Add fields to a property that does not enable dynamic mapping: it will cause an exception at some point | ||
*/ | ||
public ElasticsearchDynamicIndexedValueHolder strictProperty(String key, String value) { | ||
strictFields.put( key, value ); | ||
return this; | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
...test/java/org/hibernate/search/elasticsearch/test/bridge/MapAsInnerObjectFieldBridge.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Hibernate Search, full-text search for your domain model | ||
* | ||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later | ||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. | ||
*/ | ||
package org.hibernate.search.elasticsearch.test.bridge; | ||
|
||
import java.util.Locale; | ||
import java.util.Map; | ||
|
||
import org.apache.lucene.document.Document; | ||
import org.hibernate.search.bridge.LuceneOptions; | ||
import org.hibernate.search.bridge.MetadataProvidingFieldBridge; | ||
import org.hibernate.search.bridge.ParameterizedBridge; | ||
import org.hibernate.search.bridge.spi.FieldMetadataBuilder; | ||
import org.hibernate.search.bridge.spi.FieldMetadataCreationContext; | ||
import org.hibernate.search.bridge.spi.FieldType; | ||
import org.hibernate.search.elasticsearch.bridge.builtin.impl.Elasticsearch; | ||
import org.hibernate.search.elasticsearch.cfg.DynamicType; | ||
|
||
/** | ||
* @author Davide D'Alto | ||
*/ | ||
public class MapAsInnerObjectFieldBridge implements ParameterizedBridge, MetadataProvidingFieldBridge { | ||
|
||
public static final String DYNAMIC = "dynamicMapping"; | ||
|
||
public DynamicType dynamicType = null; | ||
|
||
@Override | ||
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) { | ||
if ( !( value instanceof Map ) ) { | ||
throw new IllegalArgumentException( "This field can only be applied on a Map type field" ); | ||
} | ||
else { | ||
Map<Object, Object> userValue = (Map) value; | ||
for ( Map.Entry<Object, Object> e : userValue.entrySet() ) { | ||
setField( name, String.valueOf( e.getKey() ), String.valueOf( e.getValue() ), document, luceneOptions ); | ||
} | ||
} | ||
} | ||
|
||
private void setField(String fieldPrefix, String key, String value, Document document, LuceneOptions luceneOptions) { | ||
luceneOptions.addFieldToDocument( fieldPrefix + "." + key, value, document ); | ||
} | ||
|
||
@Override | ||
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) { | ||
FieldMetadataCreationContext field = builder.field( name, FieldType.OBJECT ); | ||
if ( dynamicType != null ) { | ||
field.mappedOn( Elasticsearch.class ) | ||
.dynamic( dynamicType ); | ||
} | ||
} | ||
|
||
@Override | ||
public void setParameterValues(Map<String, String> parameters) { | ||
String dynamicTypeAsString = parameters.get( DYNAMIC ); | ||
if ( dynamicTypeAsString != null ) { | ||
dynamicType = DynamicType.valueOf( dynamicTypeAsString.toUpperCase( Locale.ROOT ) ); | ||
} | ||
} | ||
} |