diff --git a/engine/src/main/java/org/hibernate/search/engine/spi/DocumentBuilderIndexedEntity.java b/engine/src/main/java/org/hibernate/search/engine/spi/DocumentBuilderIndexedEntity.java index a70eac22ac3..bfa2704147a 100644 --- a/engine/src/main/java/org/hibernate/search/engine/spi/DocumentBuilderIndexedEntity.java +++ b/engine/src/main/java/org/hibernate/search/engine/spi/DocumentBuilderIndexedEntity.java @@ -681,29 +681,37 @@ public String objectToString(String fieldName, Object value, ConversionContext c throw new AssertionFailure( "Field name should not be null" ); } - DocumentFieldMetadata idFieldMetaData = idPropertyMetadata.getFieldMetadata( idFieldName ); - if ( fieldName.equals( idFieldMetaData.getName() ) ) { - return objectToString( - getIdBridge(), - idFieldMetaData.getName(), - value, - conversionContext - ); + final DocumentFieldMetadata idFieldMetaData = idPropertyMetadata.getFieldMetadata( idFieldName ); + final FieldBridge bridge = fieldName.equals( idFieldMetaData.getName() ) ? + getIdBridge() : + getBridge( getMetadata(), fieldName ); + + if ( bridge != null ) { + return objectToString( fieldName, bridge, value, conversionContext ); + } + + throw new SearchException( "Unable to find field " + fieldName + " in " + getBeanXClass() ); + } + + public String objectToString(String fieldName, FieldBridge bridge, Object value, ConversionContext conversionContext) { + if ( fieldName == null ) { + throw new AssertionFailure( "Field name should not be null" ); + } + if ( bridge == null ) { + throw new AssertionFailure( "Field bridge should not be null" ); + } + + final Class bridgeClass = bridge.getClass(); + + if ( TwoWayFieldBridge.class.isAssignableFrom( bridgeClass ) ) { + return objectToString( (TwoWayFieldBridge) bridge, fieldName, value, conversionContext ); + } + else if ( StringBridge.class.isAssignableFrom( bridgeClass ) ) { + return objectToString( (StringBridge) bridge, fieldName, value, conversionContext ); } else { - FieldBridge bridge = getBridge( getMetadata(), fieldName ); - if ( bridge != null ) { - final Class bridgeClass = bridge.getClass(); - if ( TwoWayFieldBridge.class.isAssignableFrom( bridgeClass ) ) { - return objectToString( (TwoWayFieldBridge) bridge, fieldName, value, conversionContext ); - } - else if ( StringBridge.class.isAssignableFrom( bridgeClass ) ) { - return objectToString( (StringBridge) bridge, fieldName, value, conversionContext ); - } - throw log.fieldBridgeNotTwoWay( bridgeClass, fieldName, getBeanXClass() ); - } + throw log.fieldBridgeNotTwoWay( bridgeClass, fieldName, getBeanXClass() ); } - throw new SearchException( "Unable to find field " + fieldName + " in " + getBeanXClass() ); } private FieldBridge getNullBridge(EmbeddedTypeMetadata embeddedTypeMetadata, String fieldName) { diff --git a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsRangeQueryBuilder.java b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsRangeQueryBuilder.java index 951a05fadd1..91238a779a0 100644 --- a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsRangeQueryBuilder.java +++ b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsRangeQueryBuilder.java @@ -92,13 +92,13 @@ public Query createQuery() { } private Query createQuery(FieldContext fieldContext, ConversionContext conversionContext) { - Query perFieldQuery; + final Query perFieldQuery; final String fieldName = fieldContext.getField(); final Analyzer queryAnalyzer = queryContext.getQueryAnalyzer(); final DocumentBuilderIndexedEntity documentBuilder = Helper.getDocumentBuilder( queryContext ); - FieldBridge fieldBridge = documentBuilder.getBridge( fieldContext.getField() ); + final FieldBridge fieldBridge = fieldContext.getFieldBridge() != null ? fieldContext.getFieldBridge() : documentBuilder.getBridge( fieldContext.getField() ); final Object fromObject = rangeContext.getFrom(); final Object toObject = rangeContext.getTo(); @@ -113,16 +113,12 @@ private Query createQuery(FieldContext fieldContext, ConversionContext conversio ); } else { - final String fromString = fieldContext.isIgnoreFieldBridge() ? - fromObject == null ? null : fromObject.toString() : - documentBuilder.objectToString( fieldName, fromObject, conversionContext ); + final String fromString = fieldContext.objectToString( documentBuilder, fromObject, conversionContext ); final String lowerTerm = fromString == null ? null : Helper.getAnalyzedTerm( fieldName, fromString, "from", queryAnalyzer, fieldContext ); - final String toString = fieldContext.isIgnoreFieldBridge() ? - toObject == null ? null : toObject.toString() : - documentBuilder.objectToString( fieldName, toObject, conversionContext ); + final String toString = fieldContext.objectToString( documentBuilder, toObject, conversionContext ); final String upperTerm = toString == null ? null : Helper.getAnalyzedTerm( fieldName, toString, "to", queryAnalyzer, fieldContext ); diff --git a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsTermQueryBuilder.java b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsTermQueryBuilder.java index 408987c394a..8c82b33224d 100644 --- a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsTermQueryBuilder.java +++ b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedMultiFieldsTermQueryBuilder.java @@ -36,7 +36,6 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.WildcardQuery; - import org.hibernate.annotations.common.AssertionFailure; import org.hibernate.search.SearchException; import org.hibernate.search.bridge.FieldBridge; @@ -93,12 +92,12 @@ public Query createQuery() { private Query createQuery(FieldContext fieldContext, ConversionContext conversionContext) { final Query perFieldQuery; final DocumentBuilderIndexedEntity documentBuilder = Helper.getDocumentBuilder( queryContext ); - FieldBridge fieldBridge = documentBuilder.getBridge( fieldContext.getField() ); + final FieldBridge fieldBridge = fieldContext.getFieldBridge() != null ? fieldContext.getFieldBridge() : documentBuilder.getBridge( fieldContext.getField() ); if ( fieldBridge instanceof NumericFieldBridge ) { return NumericFieldUtils.createExactMatchQuery( fieldContext.getField(), value ); } - String searchTerm = buildSearchTerm( fieldContext, documentBuilder, conversionContext ); + final String searchTerm = buildSearchTerm( fieldContext, documentBuilder, conversionContext ); if ( fieldContext.isIgnoreAnalyzer() ) { perFieldQuery = createTermQuery( fieldContext, searchTerm ); @@ -144,7 +143,7 @@ private String buildSearchTerm(FieldContext fieldContext, DocumentBuilderIndexed } else { // need to go via the appropriate bridge, because value is an object, eg boolean, and must be converted to a string first - return documentBuilder.objectToString( fieldContext.getField(), value, conversionContext ); + return fieldContext.objectToString( documentBuilder, value, conversionContext ); } } diff --git a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedRangeMatchingContext.java b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedRangeMatchingContext.java index 4ce7d8151ec..85fb0d8b96a 100644 --- a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedRangeMatchingContext.java +++ b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedRangeMatchingContext.java @@ -27,13 +27,14 @@ import java.util.ArrayList; import java.util.List; +import org.hibernate.search.bridge.FieldBridge; import org.hibernate.search.query.dsl.RangeMatchingContext; import org.hibernate.search.query.dsl.RangeTerminationExcludable; /** * @author Emmanuel Bernard */ -public class ConnectedRangeMatchingContext implements RangeMatchingContext { +public class ConnectedRangeMatchingContext implements RangeMatchingContext, FieldBridgeCustomization { private final QueryBuildingContext queryContext; private final QueryCustomizer queryCustomizer; private final RangeQueryContext rangeContext; @@ -66,7 +67,7 @@ public FromRangeContext from(T from) { } static class ConnectedFromRangeContext implements FromRangeContext { - private ConnectedRangeMatchingContext mother; + private final ConnectedRangeMatchingContext mother; ConnectedFromRangeContext(ConnectedRangeMatchingContext mother) { this.mother = mother; @@ -128,4 +129,12 @@ public RangeMatchingContext ignoreFieldBridge() { } return this; } + + @Override + public RangeMatchingContext withFieldBridge(FieldBridge fieldBridge) { + for ( FieldContext fieldContext : getCurrentFieldContexts() ) { + fieldContext.setFieldBridge( fieldBridge ); + } + return this; + } } diff --git a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedTermMatchingContext.java b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedTermMatchingContext.java index acb9a9e302d..4cd618ff618 100644 --- a/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedTermMatchingContext.java +++ b/engine/src/main/java/org/hibernate/search/query/dsl/impl/ConnectedTermMatchingContext.java @@ -27,13 +27,14 @@ import java.util.ArrayList; import java.util.List; -import org.hibernate.search.query.dsl.TermTermination; +import org.hibernate.search.bridge.FieldBridge; import org.hibernate.search.query.dsl.TermMatchingContext; +import org.hibernate.search.query.dsl.TermTermination; /** * @author Emmanuel Bernard */ -public class ConnectedTermMatchingContext implements TermMatchingContext { +public class ConnectedTermMatchingContext implements TermMatchingContext, FieldBridgeCustomization { private final QueryBuildingContext queryContext; private final QueryCustomizer queryCustomizer; private final TermQueryContext termContext; @@ -101,4 +102,12 @@ public TermMatchingContext ignoreFieldBridge() { } return this; } + + @Override + public TermMatchingContext withFieldBridge(FieldBridge fieldBridge) { + for ( FieldContext fieldContext : getCurrentFieldContexts() ) { + fieldContext.setFieldBridge( fieldBridge ); + } + return this; + } } diff --git a/engine/src/main/java/org/hibernate/search/query/dsl/impl/FieldBridgeCustomization.java b/engine/src/main/java/org/hibernate/search/query/dsl/impl/FieldBridgeCustomization.java new file mode 100644 index 00000000000..14bcd65de27 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/query/dsl/impl/FieldBridgeCustomization.java @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat, Inc. and/or its affiliates or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat, Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ + +package org.hibernate.search.query.dsl.impl; + +import org.hibernate.search.bridge.FieldBridge; + +/** + * Customization context which allows to inject a field bridge instance to be used for querying the current field. + *

+ * Not part of the public DSL API for the time being in order to gain experiences with using the functionality first; + * may be made public in a future release by merging with {@code FieldCustomization}. + * + * @author Gunnar Morling + */ +public interface FieldBridgeCustomization { + + /** + * Sets the field bridge for querying the current field; any other bridge associated with this field will be ignored + * for the query + * + * @param fieldBridge the field bridge to use + * @return this object, following the method chaining pattern + */ + T withFieldBridge(FieldBridge fieldBridge); +} diff --git a/engine/src/main/java/org/hibernate/search/query/dsl/impl/FieldContext.java b/engine/src/main/java/org/hibernate/search/query/dsl/impl/FieldContext.java index d5a6ddc91a2..8602f458dad 100644 --- a/engine/src/main/java/org/hibernate/search/query/dsl/impl/FieldContext.java +++ b/engine/src/main/java/org/hibernate/search/query/dsl/impl/FieldContext.java @@ -24,6 +24,10 @@ package org.hibernate.search.query.dsl.impl; +import org.hibernate.search.bridge.FieldBridge; +import org.hibernate.search.bridge.spi.ConversionContext; +import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity; + /** * @author Emmanuel Bernard */ @@ -32,6 +36,7 @@ public class FieldContext { private boolean ignoreAnalyzer; private final QueryCustomizer fieldCustomizer; private boolean ignoreFieldBridge; + private FieldBridge fieldBridge; public FieldContext(String field) { this.field = field; @@ -61,4 +66,24 @@ public boolean isIgnoreFieldBridge() { public void setIgnoreFieldBridge(boolean ignoreFieldBridge) { this.ignoreFieldBridge = ignoreFieldBridge; } + + public void setFieldBridge(FieldBridge fieldBridge) { + this.fieldBridge = fieldBridge; + } + + public FieldBridge getFieldBridge() { + return fieldBridge; + } + + public String objectToString(DocumentBuilderIndexedEntity documentBuilder, Object value, ConversionContext conversionContext) { + if ( isIgnoreFieldBridge() ) { + return value == null ? null : value.toString(); + } + else if ( fieldBridge != null ) { + return documentBuilder.objectToString( field, fieldBridge, value, conversionContext ); + } + else { + return documentBuilder.objectToString( field, value, conversionContext ); + } + } } diff --git a/orm/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java b/orm/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java index b0421a6f5bf..01abfed04d6 100644 --- a/orm/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java +++ b/orm/src/test/java/org/hibernate/search/test/query/dsl/DSLTest.java @@ -48,9 +48,11 @@ import org.hibernate.search.Search; import org.hibernate.search.SearchException; import org.hibernate.search.annotations.Factory; +import org.hibernate.search.bridge.builtin.impl.String2FieldBridgeAdaptor; import org.hibernate.search.cfg.SearchMapping; import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.Unit; +import org.hibernate.search.query.dsl.impl.ConnectedTermMatchingContext; import org.hibernate.search.spatial.Coordinates; import org.hibernate.search.spatial.impl.Point; import org.hibernate.search.test.SearchTestCase; @@ -101,6 +103,47 @@ public void testUseOfFieldBridge() throws Exception { transaction.commit(); } + public void testUseOfCustomFieldBridgeInstance() throws Exception { + Transaction transaction = fullTextSession.beginTransaction(); + final QueryBuilder monthQb = fullTextSession.getSearchFactory() + .buildQueryBuilder().forEntity( Month.class ).get(); + + ConnectedTermMatchingContext termMatchingContext = (ConnectedTermMatchingContext) monthQb + .keyword() + .onField( MonthClassBridge.FIELD_NAME_1 ); + + Query query = termMatchingContext + .withFieldBridge( new String2FieldBridgeAdaptor( new RomanNumberFieldBridge() ) ) + .matching( 2 ) + .createQuery(); + + assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); + transaction.commit(); + } + + public void testUseOfMultipleCustomFieldBridgeInstances() throws Exception { + Transaction transaction = fullTextSession.beginTransaction(); + final QueryBuilder monthQb = fullTextSession.getSearchFactory() + .buildQueryBuilder().forEntity( Month.class ).get(); + + //Rather complex code here as we're not exposing the #withFieldBridge methods on the public interface + final ConnectedTermMatchingContext field1Context = (ConnectedTermMatchingContext) monthQb + .keyword() + .onField( MonthClassBridge.FIELD_NAME_1 ); + + final ConnectedTermMatchingContext field2Context = (ConnectedTermMatchingContext) field1Context + .withFieldBridge( new String2FieldBridgeAdaptor( new RomanNumberFieldBridge() ) ) + .andField( MonthClassBridge.FIELD_NAME_2 ); + + Query query = field2Context + .withFieldBridge( new String2FieldBridgeAdaptor( new RomanNumberFieldBridge() ) ) + .matching( 2 ) + .createQuery(); + + assertEquals( 1, fullTextSession.createFullTextQuery( query, Month.class ).getResultSize() ); + transaction.commit(); + } + public void testTermQueryOnAnalyzer() throws Exception { Transaction transaction = fullTextSession.beginTransaction(); final QueryBuilder monthQb = fullTextSession.getSearchFactory() diff --git a/orm/src/test/java/org/hibernate/search/test/query/dsl/Month.java b/orm/src/test/java/org/hibernate/search/test/query/dsl/Month.java index 84ed4c71fd3..d84f99bb2f5 100644 --- a/orm/src/test/java/org/hibernate/search/test/query/dsl/Month.java +++ b/orm/src/test/java/org/hibernate/search/test/query/dsl/Month.java @@ -25,12 +25,14 @@ package org.hibernate.search.test.query.dsl; import java.util.Date; + import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.Analyzer; +import org.hibernate.search.annotations.ClassBridge; import org.hibernate.search.annotations.DateBridge; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.FieldBridge; @@ -45,6 +47,7 @@ */ @Entity @Indexed +@ClassBridge(impl = MonthClassBridge.class) public class Month { public Month() { diff --git a/orm/src/test/java/org/hibernate/search/test/query/dsl/MonthClassBridge.java b/orm/src/test/java/org/hibernate/search/test/query/dsl/MonthClassBridge.java new file mode 100644 index 00000000000..4c644ed34d2 --- /dev/null +++ b/orm/src/test/java/org/hibernate/search/test/query/dsl/MonthClassBridge.java @@ -0,0 +1,56 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, Red Hat, Inc. and/or its affiliates or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat, Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.search.test.query.dsl; + +import org.apache.lucene.document.Document; +import org.hibernate.search.bridge.FieldBridge; +import org.hibernate.search.bridge.LuceneOptions; + +/** + * Adds a custom field to be queried via explicitly passed field bridge. + * + * @author Gunnar Morling + */ +public class MonthClassBridge implements FieldBridge { + + public static final String FIELD_NAME_1 = "monthValueAsRomanNumberFromClassBridge1"; + public static final String FIELD_NAME_2 = "monthValueAsRomanNumberFromClassBridge2"; + + @Override + public void set(String name, Object value, Document document, LuceneOptions luceneOptions) { + Month month = (Month) value; + + luceneOptions.addFieldToDocument( + FIELD_NAME_1, + new RomanNumberFieldBridge().objectToString( month.getMonthValue() ), + document + ); + + luceneOptions.addFieldToDocument( + FIELD_NAME_2, + new RomanNumberFieldBridge().objectToString( month.getMonthValue() ), + document + ); + } +}