Skip to content

Commit

Permalink
HSEARCH-4574 Move projection constructor binding to dedicated classes
Browse files Browse the repository at this point in the history
To prepare for the introduction of projection binders.
  • Loading branch information
yrodiere committed May 31, 2023
1 parent 2fd171a commit a55214c
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 192 deletions.
@@ -0,0 +1,108 @@
/*
* 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.mapper.pojo.search.definition.binding.impl;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.search.engine.backend.common.spi.FieldPaths;
import org.hibernate.search.mapper.pojo.logging.impl.Log;
import org.hibernate.search.mapper.pojo.mapping.building.impl.PojoMappingHelper;
import org.hibernate.search.mapper.pojo.model.spi.PojoConstructorModel;
import org.hibernate.search.mapper.pojo.model.spi.PojoMethodParameterModel;
import org.hibernate.search.mapper.pojo.reporting.spi.PojoEventContexts;
import org.hibernate.search.mapper.pojo.search.definition.impl.InnerProjectionDefinition;
import org.hibernate.search.mapper.pojo.search.definition.impl.NullInnerProjectionDefinition;
import org.hibernate.search.mapper.pojo.search.definition.impl.PojoConstructorProjectionDefinition;
import org.hibernate.search.util.common.logging.impl.LoggerFactory;
import org.hibernate.search.util.common.reporting.EventContext;
import org.hibernate.search.util.common.reporting.spi.EventContextProvider;

public class ProjectionConstructorBinder<T> implements EventContextProvider {

private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );
private final PojoMappingHelper mappingHelper;
private final ProjectionConstructorParameterBinder<?> parent;
private final String relativeFieldPath;
final String absoluteFieldPath;
final PojoConstructorModel<T> constructor;

public ProjectionConstructorBinder(PojoMappingHelper mappingHelper, PojoConstructorModel<T> constructor) {
this( mappingHelper, constructor, null, null );
}

ProjectionConstructorBinder(PojoMappingHelper mappingHelper, PojoConstructorModel<T> constructor,
ProjectionConstructorParameterBinder<?> parent, String relativeFieldPath) {
this.mappingHelper = mappingHelper;
this.parent = parent;
this.relativeFieldPath = relativeFieldPath;
this.absoluteFieldPath = FieldPaths.compose( parent == null ? null : parent.parent.absoluteFieldPath,
relativeFieldPath );
this.constructor = constructor;
}

@Override
public EventContext eventContext() {
return EventContext.concat(
parent == null ? null : parent.eventContext(),
PojoEventContexts.fromType( constructor.typeModel() ),
PojoEventContexts.fromConstructor( constructor )
);
}

public PojoConstructorProjectionDefinition<T> bind() {
if ( constructor.typeModel().isAbstract() ) {
throw log.invalidAbstractTypeForProjectionConstructor(
constructor.typeModel() );
}
if ( parent != null ) {
String path = parent.parent.getPathFromSameProjectionConstructor( constructor );
if ( path != null ) {
throw log.infiniteRecursionForProjectionConstructor(
constructor,
FieldPaths.compose( path, relativeFieldPath )
);
}
}
List<InnerProjectionDefinition> innerDefinitions = new ArrayList<>();
for ( PojoMethodParameterModel<?> parameter : constructor.declaredParameters() ) {
ProjectionConstructorParameterBinder<?> parameterNode =
new ProjectionConstructorParameterBinder<>(
mappingHelper, this, parameter );
InnerProjectionDefinition innerProjection;
try {
innerProjection = parameterNode.bind();
}
catch (RuntimeException e) {
mappingHelper.failureCollector()
.withContext( parameterNode.eventContext() )
.add( e );
// Here the result no longer matters, bootstrap will fail anyway.
// We just pick a neutral value that won't trigger cascading failures.
innerProjection = NullInnerProjectionDefinition.INSTANCE;
}
innerDefinitions.add( innerProjection );
}
return new PojoConstructorProjectionDefinition<>( constructor, innerDefinitions );
}

private String getPathFromSameProjectionConstructor(PojoConstructorModel<?> constructorToMatch) {
if ( this.constructor.equals( constructorToMatch ) ) {
return "";
}
else if ( parent != null ) {
String path = parent.parent.getPathFromSameProjectionConstructor( constructorToMatch );
return path == null ? null : FieldPaths.compose( path, relativeFieldPath );
}
else {
// This is the root.
// If we reach this point, no definition involving the given projection constructor was found.
return null;
}
}
}
@@ -0,0 +1,126 @@
/*
* 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.mapper.pojo.search.definition.binding.impl;

import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Optional;

import org.hibernate.search.engine.backend.common.spi.FieldPaths;
import org.hibernate.search.mapper.pojo.extractor.builtin.BuiltinContainerExtractors;
import org.hibernate.search.mapper.pojo.extractor.impl.BoundContainerExtractorPath;
import org.hibernate.search.mapper.pojo.extractor.mapping.programmatic.ContainerExtractorPath;
import org.hibernate.search.mapper.pojo.logging.impl.Log;
import org.hibernate.search.mapper.pojo.mapping.building.impl.PojoMappingHelper;
import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoSearchMappingConstructorNode;
import org.hibernate.search.mapper.pojo.mapping.building.spi.PojoTypeMetadataContributor;
import org.hibernate.search.mapper.pojo.model.spi.PojoConstructorModel;
import org.hibernate.search.mapper.pojo.model.spi.PojoMethodParameterModel;
import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeModel;
import org.hibernate.search.mapper.pojo.model.spi.PojoTypeModel;
import org.hibernate.search.mapper.pojo.reporting.spi.PojoEventContexts;
import org.hibernate.search.mapper.pojo.search.definition.impl.InnerProjectionDefinition;
import org.hibernate.search.mapper.pojo.search.definition.impl.NullInnerProjectionDefinition;
import org.hibernate.search.mapper.pojo.search.definition.impl.ObjectInnerProjectionDefinition;
import org.hibernate.search.mapper.pojo.search.definition.impl.PojoConstructorProjectionDefinition;
import org.hibernate.search.mapper.pojo.search.definition.impl.ValueInnerProjectionDefinition;
import org.hibernate.search.util.common.logging.impl.LoggerFactory;
import org.hibernate.search.util.common.reporting.EventContext;
import org.hibernate.search.util.common.reporting.spi.EventContextProvider;

class ProjectionConstructorParameterBinder<P> implements EventContextProvider {

private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() );
private final PojoMappingHelper mappingHelper;
final ProjectionConstructorBinder<?> parent;
private final PojoMethodParameterModel<P> parameter;

ProjectionConstructorParameterBinder(PojoMappingHelper mappingHelper, ProjectionConstructorBinder<?> parent,
PojoMethodParameterModel<P> parameter) {
this.mappingHelper = mappingHelper;
this.parent = parent;
this.parameter = parameter;
}

@Override
public EventContext eventContext() {
return parent.eventContext().append( PojoEventContexts.fromMethodParameter( parameter ) );
}

InnerProjectionDefinition bind() {
if ( parameter.isEnclosingInstance() ) {
// Let's ignore this parameter, because we are not able to provide a surrounding instance,
// and it's often useful to be able to declare a method-local type for projections
// (those types have a "surrounding instance" parameter in their constructor
// even if they don't use it).
return NullInnerProjectionDefinition.INSTANCE;
}
PojoTypeModel<P> parameterType = parameter.typeModel();
BoundContainerExtractorPath<?, ?> boundParameterElementPath = mappingHelper.indexModelBinder()
.bindExtractorPath( parameter.typeModel(), ContainerExtractorPath.defaultExtractors() );
List<String> boundParameterElementExtractorNames =
boundParameterElementPath.getExtractorPath().explicitExtractorNames();

boolean multi;
if ( boundParameterElementExtractorNames.isEmpty() ) {
multi = false;
}
else {
if ( boundParameterElementExtractorNames.size() > 1
|| !( BuiltinContainerExtractors.COLLECTION.equals( boundParameterElementExtractorNames.get( 0 ) )
|| BuiltinContainerExtractors.ITERABLE.equals( boundParameterElementExtractorNames.get( 0 ) ) )
|| !mappingHelper.introspector().typeModel( List.class ).isSubTypeOf(
parameterType.rawType().rawType() ) ) {
throw log.invalidMultiValuedParameterTypeForProjectionConstructor(
parameterType );
}
multi = true;
}

PojoRawTypeModel<?> boundParameterElementRawType = boundParameterElementPath.getExtractedType().rawType();
Optional<String> paramName = parameter.name();
if ( !paramName.isPresent() ) {
throw log.missingParameterNameForProjectionConstructor();
}
String innerRelativeFieldPath = paramName.get();
String innerAbsoluteFieldPath = FieldPaths.compose( parent.absoluteFieldPath, innerRelativeFieldPath );
PojoConstructorProjectionDefinition<?> definition = createConstructorProjectionDefinitionOrNull(
boundParameterElementRawType, innerRelativeFieldPath );
if ( definition != null ) {
return new ObjectInnerProjectionDefinition( innerAbsoluteFieldPath, multi, definition );
}
else {
// No projection constructor for this type; assume it's a projection on value field
return new ValueInnerProjectionDefinition( innerAbsoluteFieldPath, multi,
boundParameterElementRawType.typeIdentifier().javaClass()
);
}
}

<T> PojoConstructorProjectionDefinition<T> createConstructorProjectionDefinitionOrNull(
PojoRawTypeModel<T> parameterType,
String relativeFieldPath) {
PojoConstructorProjectionDefinition<T> result = null;
for ( PojoTypeMetadataContributor contributor : mappingHelper.contributorProvider()
// Constructor mapping is not inherited
.getIgnoringInheritance( parameterType ) ) {
for ( PojoSearchMappingConstructorNode constructorMapping : contributor.constructors().values() ) {
if ( constructorMapping.isProjectionConstructor() ) {
if ( result != null ) {
throw log.multipleProjectionConstructorsForType(
parameterType.typeIdentifier().javaClass() );
}
PojoConstructorModel<T> constructor = parameterType.constructor(
constructorMapping.parametersJavaTypes() );
result = new ProjectionConstructorBinder<>( mappingHelper, constructor, this, relativeFieldPath )
.bind();
}
}
}
return result;
}
}
Expand Up @@ -10,7 +10,7 @@
import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory;
import org.hibernate.search.util.common.spi.ToStringTreeAppendable;

interface InnerProjectionDefinition extends ToStringTreeAppendable {
public interface InnerProjectionDefinition extends ToStringTreeAppendable {

SearchProjection<?> create(SearchProjectionFactory<?, ?> f);

Expand Down
Expand Up @@ -10,9 +10,9 @@
import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory;
import org.hibernate.search.util.common.spi.ToStringTreeAppender;

final class NullInnerProjectionDefinition
public final class NullInnerProjectionDefinition
implements InnerProjectionDefinition {
static final NullInnerProjectionDefinition INSTANCE = new NullInnerProjectionDefinition();
public static final NullInnerProjectionDefinition INSTANCE = new NullInnerProjectionDefinition();

private NullInnerProjectionDefinition() {
}
Expand Down
Expand Up @@ -11,12 +11,12 @@
import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory;
import org.hibernate.search.util.common.spi.ToStringTreeAppender;

final class ObjectInnerProjectionDefinition implements InnerProjectionDefinition {
public final class ObjectInnerProjectionDefinition implements InnerProjectionDefinition {
final String path;
final boolean multi;
final CompositeProjectionDefinition<?> composite;

ObjectInnerProjectionDefinition(String path, boolean multi, CompositeProjectionDefinition<?> composite) {
public ObjectInnerProjectionDefinition(String path, boolean multi, CompositeProjectionDefinition<?> composite) {
this.path = path;
this.multi = multi;
this.composite = composite;
Expand Down

0 comments on commit a55214c

Please sign in to comment.