diff --git a/engine/src/main/java/org/hibernate/search/engine/metadata/impl/AnnotationMetadataProvider.java b/engine/src/main/java/org/hibernate/search/engine/metadata/impl/AnnotationMetadataProvider.java index 09b24f85a55..817276c1bdf 100644 --- a/engine/src/main/java/org/hibernate/search/engine/metadata/impl/AnnotationMetadataProvider.java +++ b/engine/src/main/java/org/hibernate/search/engine/metadata/impl/AnnotationMetadataProvider.java @@ -102,6 +102,8 @@ */ @SuppressWarnings( "deprecation" ) public class AnnotationMetadataProvider implements MetadataProvider { + private static final int INFINITE_DEPTH = Integer.MAX_VALUE; + private static final Log log = LoggerFactory.make(); private static final StringBridge NULL_EMBEDDED_STRING_BRIDGE = DefaultStringBridge.INSTANCE; private static final String UNKNOWN_MAPPED_BY_ROLE = ""; @@ -999,7 +1001,7 @@ private ContainedInMetadata createContainedInMetadata(XProperty member) { } private void updateContainedInMetadata(ContainedInMetadataBuilder containedInMetadataBuilder, XProperty propertyWithContainedIn, String accessType) { - XClass memberReturnedType = propertyWithContainedIn.getElementClass(); + XClass memberReturnedType = returnedType( propertyWithContainedIn ); String mappedBy = mappedBy( propertyWithContainedIn ); List returnedTypeProperties = memberReturnedType.getDeclaredProperties( accessType ); for ( XProperty property : returnedTypeProperties ) { @@ -1298,7 +1300,7 @@ private FacetEncodingType determineFacetEncodingType(XProperty member, Facet fac return facetEncodingType; // encoding type explicitly set } - Class indexedType = reflectionManager.toClass( member.getElementClass() ); + Class indexedType = reflectionManager.toClass( returnedType( member ) ); if ( ReflectionHelper.isIntegerType( indexedType ) ) { facetEncodingType = FacetEncodingType.LONG; } @@ -1672,40 +1674,35 @@ private void checkForIndexedEmbedded( return; } + boolean elementCollection = isElementCollection( member ); + if ( elementCollection ) { + // @IndexEmbedded used with @ElementCollection + logWarnings( member, indexedEmbeddedAnnotation ); + } + parseContext.collectUnqualifiedCollectionRole( member.getName() ); int oldMaxLevel = parseContext.getMaxLevel(); - int potentialLevel = depth( indexedEmbeddedAnnotation ) + parseContext.getLevel(); - // This is really catching a possible int overflow. depth() can return Integer.MAX_VALUE, which then can - // overflow in case level > 0. Really this code should be rewritten (HF) - if ( potentialLevel < 0 ) { - potentialLevel = Integer.MAX_VALUE; - } - // HSEARCH-1442 recreating the behavior prior to PropertiesMetadata refactoring + int potentialLevel = elementCollection ? INFINITE_DEPTH : potentialLevel( parseContext, indexedEmbeddedAnnotation, member ); + // HSEARCH-1442 recreating the behaviour prior to PropertiesMetadata refactoring // not sure whether this is algorithmically correct though. @IndexedEmbedded processing should be refactored (HF) if ( potentialLevel < oldMaxLevel ) { parseContext.setMaxLevel( potentialLevel ); } parseContext.incrementLevel(); - XClass elementClass; - if ( void.class == indexedEmbeddedAnnotation.targetElement() ) { - elementClass = member.getElementClass(); - } - else { - elementClass = reflectionManager.toXClass( indexedEmbeddedAnnotation.targetElement() ); - } + XClass elementClass = elementCollection ? returnedType( member ) : elementClass( member, indexedEmbeddedAnnotation ); + String localPrefix = elementCollection ? defaultPrefix( member ) : buildEmbeddedPrefix( prefix, indexedEmbeddedAnnotation, member ); + boolean includeEmbeddedObjectId = elementCollection ? false : indexedEmbeddedAnnotation.includeEmbeddedObjectId(); - if ( parseContext.getMaxLevel() == Integer.MAX_VALUE //infinite + if ( parseContext.getMaxLevel() == INFINITE_DEPTH && parseContext.hasBeenProcessed( elementClass ) ) { throw log.detectInfiniteTypeLoopInIndexedEmbedded( elementClass.getName(), typeMetadataBuilder.getIndexedType().getName(), - buildEmbeddedPrefix( prefix, indexedEmbeddedAnnotation, member ) - ); + localPrefix ); } - String localPrefix = buildEmbeddedPrefix( prefix, indexedEmbeddedAnnotation, member ); PathsContext updatedPathsContext = updatePaths( localPrefix, pathsContext, indexedEmbeddedAnnotation ); boolean pathsCreatedAtThisLevel = false; @@ -1752,7 +1749,7 @@ private void checkForIndexedEmbedded( XClass previousClass = parseContext.getCurrentClass(); parseContext.setCurrentClass( elementClass ); boolean previousIncludeEmbeddedObjectId = parseContext.includeEmbeddedObjectId(); - parseContext.setIncludeEmbeddedObjectId( indexedEmbeddedAnnotation.includeEmbeddedObjectId() ); + parseContext.setIncludeEmbeddedObjectId( includeEmbeddedObjectId ); initializeClass( embeddedTypeMetadataBuilder, false, @@ -1797,15 +1794,65 @@ else if ( log.isTraceEnabled() ) { } } + private void logWarnings(XProperty member, IndexedEmbedded indexedEmbeddedAnnotation) { + if ( isDepthSet( indexedEmbeddedAnnotation ) ) { + log.indexEmbeddedValueIgnored( member.getName(), "depth" ); + } + if ( void.class != indexedEmbeddedAnnotation.targetElement() ) { + log.indexEmbeddedValueIgnored( member.getName(), "targetElement" ); + } + if ( !isDefaultPrefix( indexedEmbeddedAnnotation ) ) { + log.indexEmbeddedValueIgnored( member.getName(), "prefix" ); + } + if ( indexedEmbeddedAnnotation.includeEmbeddedObjectId() ) { + log.indexEmbeddedValueIgnored( member.getName(), "includeEmbeddedObjectId" ); + } + } + + private int potentialLevel(ParseContext parseContext, IndexedEmbedded indexedEmbeddedAnnotation, XProperty member) { + int potentialLevel = depth( indexedEmbeddedAnnotation ) + parseContext.getLevel(); + // This is really catching a possible int overflow. depth() can return Integer.MAX_VALUE, which then can + // overflow in case level > 0. Really this code should be rewritten (HF) + if ( potentialLevel < 0 ) { + potentialLevel = INFINITE_DEPTH; + } + return potentialLevel; + } + + private XClass elementClass(XProperty member, IndexedEmbedded indexedEmbeddedAnnotation) { + if ( void.class == indexedEmbeddedAnnotation.targetElement() ) { + return returnedType( member ); + } + else { + return reflectionManager.toXClass( indexedEmbeddedAnnotation.targetElement() ); + } + } + + private XClass returnedType(XProperty member) { + return member.getElementClass(); + } + + private boolean isElementCollection(XProperty member) { + Annotation[] annotations = member.getAnnotations(); + for ( Annotation annotation : annotations ) { + String type = annotation.annotationType().getName(); + // Using String because the annotation class might not be on the classpath + if ( "javax.persistence.ElementCollection".equals( type ) ) { + return true; + } + } + return false; + } + private int depth(IndexedEmbedded embeddedAnn) { - if ( isDepthNotSet( embeddedAnn ) && embeddedAnn.includePaths().length > 0 ) { + if ( !isDepthSet( embeddedAnn ) && embeddedAnn.includePaths().length > 0 ) { return 0; } return embeddedAnn.depth(); } - private boolean isDepthNotSet(IndexedEmbedded embeddedAnn) { - return Integer.MAX_VALUE == embeddedAnn.depth(); + private boolean isDepthSet(IndexedEmbedded embeddedAnn) { + return INFINITE_DEPTH != embeddedAnn.depth(); } private boolean isInPath(String localPrefix, PathsContext pathsContext, IndexedEmbedded embeddedAnn) { @@ -1839,7 +1886,7 @@ private String buildEmbeddedPrefix(String prefix, IndexedEmbedded indexedEmbedde String localPrefix = prefix; if ( isDefaultPrefix( indexedEmbeddedAnnotation ) ) { //default to property name - localPrefix += member.getName() + '.'; + localPrefix += defaultPrefix( member ); } else { localPrefix += indexedEmbeddedAnnotation.prefix(); @@ -1847,6 +1894,10 @@ private String buildEmbeddedPrefix(String prefix, IndexedEmbedded indexedEmbedde return localPrefix; } + private String defaultPrefix(XProperty member) { + return member.getName() + '.'; + } + private boolean isDefaultPrefix(IndexedEmbedded indexedEmbeddedAnnotation) { return ".".equals( indexedEmbeddedAnnotation.prefix() ); } diff --git a/engine/src/main/java/org/hibernate/search/util/logging/impl/Log.java b/engine/src/main/java/org/hibernate/search/util/logging/impl/Log.java index 09175d351cf..6b7d344b431 100644 --- a/engine/src/main/java/org/hibernate/search/util/logging/impl/Log.java +++ b/engine/src/main/java/org/hibernate/search/util/logging/impl/Log.java @@ -970,4 +970,7 @@ public interface Log extends BasicLogger { @Message(id = 315, value = "Lazy remote analyzer %1$s is not initialized.") SearchException lazyRemoteAnalyzerNotInitialized(LazyRemoteAnalyzer analyzer); + @LogMessage(level = Level.WARN) + @Message(id = 316, value = "The value for @IndexEmbedded#%2$s() on the property `%1$s` is ignored when the annotation is used with @ElementCollection") + void indexEmbeddedValueIgnored(String propertyName, String indexEmbeddedAttribute); } diff --git a/orm/src/test/java/org/hibernate/search/test/bridge/ArrayBridgeTest.java b/orm/src/test/java/org/hibernate/search/test/bridge/ArrayBridgeTest.java index e4180fcb8f3..f719d7a98da 100644 --- a/orm/src/test/java/org/hibernate/search/test/bridge/ArrayBridgeTest.java +++ b/orm/src/test/java/org/hibernate/search/test/bridge/ArrayBridgeTest.java @@ -104,11 +104,11 @@ public void testSearchNullEmbedded() throws Exception { @Test public void testSearchNullNumericEmbedded() throws Exception { List results = - findEmbeddedNullResults( "embeddedNum", ArrayBridgeTestEntity.NULL_EMBEDDED_NUMERIC, true ); + findEmbeddedNullResults( "numericNullIndexed", ArrayBridgeTestEntity.NULL_EMBEDDED_NUMERIC, true ); assertNotNull( "No result found for an indexed collection", results ); assertEquals( "Unexpected number of results in a collection", 1, results.size() ); - assertEquals( "Wrong result returned looking for a null in a collection of numeric", withNullEmbedded.getName(), results.get( 0 ).getName() ); + assertEquals( "Wrong result returned looking for a null collection", withNullEmbedded.getName(), results.get( 0 ).getName() ); } @Test diff --git a/orm/src/test/java/org/hibernate/search/test/bridge/IterableBridgeTest.java b/orm/src/test/java/org/hibernate/search/test/bridge/IterableBridgeTest.java index 33f4a8ff526..bc1f7876370 100644 --- a/orm/src/test/java/org/hibernate/search/test/bridge/IterableBridgeTest.java +++ b/orm/src/test/java/org/hibernate/search/test/bridge/IterableBridgeTest.java @@ -113,7 +113,7 @@ public void testSearchNullEmbedded() throws Exception { @Test public void testSearchNullNumericEmbedded() throws Exception { List results = - findEmbeddedNullResults( "embeddedNum", IterableBridgeTestEntity.NULL_EMBEDDED_NUMERIC, true ); + findEmbeddedNullResults( "numericNullIndexed", IterableBridgeTestEntity.NULL_EMBEDDED_NUMERIC, true ); assertNotNull( "No result found for an indexed collection", results ); assertEquals( "Unexpected number of results in a collection", 1, results.size() ); diff --git a/orm/src/test/java/org/hibernate/search/test/bridge/MapBridgeTest.java b/orm/src/test/java/org/hibernate/search/test/bridge/MapBridgeTest.java index a9f361067d9..74ac6f8b149 100644 --- a/orm/src/test/java/org/hibernate/search/test/bridge/MapBridgeTest.java +++ b/orm/src/test/java/org/hibernate/search/test/bridge/MapBridgeTest.java @@ -111,7 +111,7 @@ public void testSearchNullEmbedded() throws Exception { @Test public void testSearchNullNumericEmbedded() throws Exception { List results = - findEmbeddedNullResults( "embeddedNum", MapBridgeTestEntity.NULL_EMBEDDED_NUMERIC, true ); + findEmbeddedNullResults( "numericNullIndexed", MapBridgeTestEntity.NULL_EMBEDDED_NUMERIC, true ); assertNotNull( "No result found for an indexed collection", results ); assertEquals( "Unexpected number of results in a collection", 1, results.size() );