mirror of
				https://github.com/spring-projects/spring-data-elasticsearch.git
				synced 2025-10-31 06:38:44 +00:00 
			
		
		
		
	Support field exclusion from source.
Original Pull Request #1962 Closes #769
This commit is contained in:
		
							parent
							
								
									59fdbbeb19
								
							
						
					
					
						commit
						288705ca72
					
				| @ -203,4 +203,12 @@ public @interface Field { | |||||||
| 	 * @since 4.3 | 	 * @since 4.3 | ||||||
| 	 */ | 	 */ | ||||||
| 	Dynamic dynamic() default Dynamic.INHERIT; | 	Dynamic dynamic() default Dynamic.INHERIT; | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * marks this field to be excluded from the _source in Elasticsearch | ||||||
|  | 	 * (https://www.elastic.co/guide/en/elasticsearch/reference/7.15.0/mapping-source-field.html#include-exclude) | ||||||
|  | 	 * | ||||||
|  | 	 * @since 4.3 | ||||||
|  | 	 */ | ||||||
|  | 	boolean excludeFromSource() default false; | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,8 +21,10 @@ import static org.springframework.util.StringUtils.*; | |||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.lang.annotation.Annotation; | import java.lang.annotation.Annotation; | ||||||
| import java.nio.charset.Charset; | import java.nio.charset.Charset; | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
|  | import java.util.List; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| @ -102,12 +104,12 @@ public class MappingBuilder { | |||||||
| 	private static final String NUMERIC_DETECTION = "numeric_detection"; | 	private static final String NUMERIC_DETECTION = "numeric_detection"; | ||||||
| 	private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats"; | 	private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats"; | ||||||
| 	private static final String RUNTIME = "runtime"; | 	private static final String RUNTIME = "runtime"; | ||||||
|  | 	private static final String SOURCE = "_source"; | ||||||
|  | 	private static final String SOURCE_EXCLUDES = "excludes"; | ||||||
| 
 | 
 | ||||||
| 	protected final ElasticsearchConverter elasticsearchConverter; | 	protected final ElasticsearchConverter elasticsearchConverter; | ||||||
| 	private final ObjectMapper objectMapper = new ObjectMapper(); | 	private final ObjectMapper objectMapper = new ObjectMapper(); | ||||||
| 
 | 
 | ||||||
| 	private boolean writeTypeHints = true; |  | ||||||
| 
 |  | ||||||
| 	public MappingBuilder(ElasticsearchConverter elasticsearchConverter) { | 	public MappingBuilder(ElasticsearchConverter elasticsearchConverter) { | ||||||
| 		this.elasticsearchConverter = elasticsearchConverter; | 		this.elasticsearchConverter = elasticsearchConverter; | ||||||
| 	} | 	} | ||||||
| @ -129,116 +131,8 @@ public class MappingBuilder { | |||||||
| 	protected String buildPropertyMapping(ElasticsearchPersistentEntity<?> entity, | 	protected String buildPropertyMapping(ElasticsearchPersistentEntity<?> entity, | ||||||
| 			@Nullable org.springframework.data.elasticsearch.core.document.Document runtimeFields) { | 			@Nullable org.springframework.data.elasticsearch.core.document.Document runtimeFields) { | ||||||
| 
 | 
 | ||||||
| 		try { | 		InternalBuilder internalBuilder = new InternalBuilder(); | ||||||
| 
 | 		return internalBuilder.buildPropertyMapping(entity, runtimeFields); | ||||||
| 			writeTypeHints = entity.writeTypeHints(); |  | ||||||
| 
 |  | ||||||
| 			ObjectNode objectNode = objectMapper.createObjectNode(); |  | ||||||
| 
 |  | ||||||
| 			// Dynamic templates |  | ||||||
| 			addDynamicTemplatesMapping(objectNode, entity); |  | ||||||
| 
 |  | ||||||
| 			mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class), |  | ||||||
| 					runtimeFields); |  | ||||||
| 
 |  | ||||||
| 			return objectMapper.writer().writeValueAsString(objectNode); |  | ||||||
| 		} catch (IOException e) { |  | ||||||
| 			throw new MappingException("could not build mapping", e); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private void writeTypeHintMapping(ObjectNode propertiesNode) throws IOException { |  | ||||||
| 
 |  | ||||||
| 		if (writeTypeHints) { |  | ||||||
| 			propertiesNode.set(TYPEHINT_PROPERTY, objectMapper.createObjectNode() // |  | ||||||
| 					.put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // |  | ||||||
| 					.put(FIELD_PARAM_INDEX, false) // |  | ||||||
| 					.put(FIELD_PARAM_DOC_VALUES, false)); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity, boolean isRootObject, |  | ||||||
| 			String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, |  | ||||||
| 			@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping, @Nullable Document runtimeFields) |  | ||||||
| 			throws IOException { |  | ||||||
| 
 |  | ||||||
| 		if (entity != null && entity.isAnnotationPresent(Mapping.class)) { |  | ||||||
| 			Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class); |  | ||||||
| 
 |  | ||||||
| 			if (!mappingAnnotation.enabled()) { |  | ||||||
| 				objectNode.put(MAPPING_ENABLED, false); |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (mappingAnnotation.dateDetection() != Mapping.Detection.DEFAULT) { |  | ||||||
| 				objectNode.put(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name())); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (mappingAnnotation.numericDetection() != Mapping.Detection.DEFAULT) { |  | ||||||
| 				objectNode.put(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name())); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (mappingAnnotation.dynamicDateFormats().length > 0) { |  | ||||||
| 				objectNode.putArray(DYNAMIC_DATE_FORMATS).addAll( |  | ||||||
| 						Arrays.stream(mappingAnnotation.dynamicDateFormats()).map(TextNode::valueOf).collect(Collectors.toList())); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (runtimeFields != null) { |  | ||||||
| 				objectNode.set(RUNTIME, objectMapper.convertValue(runtimeFields, JsonNode.class)); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField); |  | ||||||
| 		if (writeNestedProperties) { |  | ||||||
| 
 |  | ||||||
| 			String type = nestedOrObjectField ? fieldType.toString().toLowerCase() |  | ||||||
| 					: FieldType.Object.toString().toLowerCase(); |  | ||||||
| 
 |  | ||||||
| 			ObjectNode nestedObjectNode = objectMapper.createObjectNode(); |  | ||||||
| 			nestedObjectNode.put(FIELD_PARAM_TYPE, type); |  | ||||||
| 
 |  | ||||||
| 			if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null |  | ||||||
| 					&& parentFieldAnnotation.includeInParent()) { |  | ||||||
| 				nestedObjectNode.put(FIELD_INCLUDE_IN_PARENT, true); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			objectNode.set(nestedObjectFieldName, nestedObjectNode); |  | ||||||
| 			// now go on with the nested one |  | ||||||
| 			objectNode = nestedObjectNode; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (entity != null && entity.dynamic() != Dynamic.INHERIT) { |  | ||||||
| 			objectNode.put(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase()); |  | ||||||
| 		} else if (dynamicMapping != null) { |  | ||||||
| 			objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		ObjectNode propertiesNode = objectNode.putObject(FIELD_PROPERTIES); |  | ||||||
| 
 |  | ||||||
| 		writeTypeHintMapping(propertiesNode); |  | ||||||
| 
 |  | ||||||
| 		if (entity != null) { |  | ||||||
| 			entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> { |  | ||||||
| 				try { |  | ||||||
| 					if (property.isAnnotationPresent(Transient.class) || isInIgnoreFields(property, parentFieldAnnotation)) { |  | ||||||
| 						return; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					if (property.isSeqNoPrimaryTermProperty()) { |  | ||||||
| 						if (property.isAnnotationPresent(Field.class)) { |  | ||||||
| 							logger.warn("Property {} of {} is annotated for inclusion in mapping, but its type is " + // |  | ||||||
| 							"SeqNoPrimaryTerm that is never mapped, so it is skipped", // |  | ||||||
| 									property.getFieldName(), entity.getType()); |  | ||||||
| 						} |  | ||||||
| 						return; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					buildPropertyMapping(propertiesNode, isRootObject, property); |  | ||||||
| 				} catch (IOException e) { |  | ||||||
| 					logger.warn("error mapping property with name {}", property.getName(), e); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Nullable | 	@Nullable | ||||||
| @ -259,315 +153,457 @@ public class MappingBuilder { | |||||||
| 		return null; | 		return null; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	private void buildPropertyMapping(ObjectNode propertiesNode, boolean isRootObject, | 	private class InternalBuilder { | ||||||
| 			ElasticsearchPersistentProperty property) throws IOException { |  | ||||||
| 
 | 
 | ||||||
| 		if (property.isAnnotationPresent(Mapping.class)) { | 		private boolean writeTypeHints = true; | ||||||
|  | 		private List<String> excludeFromSource = new ArrayList<>(); | ||||||
|  | 		private String nestedPropertyPrefix = ""; | ||||||
| 
 | 
 | ||||||
| 			Mapping mapping = property.getRequiredAnnotation(Mapping.class); | 		protected String buildPropertyMapping(ElasticsearchPersistentEntity<?> entity, | ||||||
|  | 				@Nullable org.springframework.data.elasticsearch.core.document.Document runtimeFields) { | ||||||
| 
 | 
 | ||||||
| 			if (mapping.enabled()) { | 			try { | ||||||
| 				String mappingPath = mapping.mappingPath(); |  | ||||||
| 
 | 
 | ||||||
| 				if (StringUtils.hasText(mappingPath)) { | 				writeTypeHints = entity.writeTypeHints(); | ||||||
| 
 | 
 | ||||||
| 					ClassPathResource mappings = new ClassPathResource(mappingPath); | 				ObjectNode objectNode = objectMapper.createObjectNode(); | ||||||
| 					if (mappings.exists()) { | 
 | ||||||
| 						propertiesNode.putRawValue(property.getFieldName(), | 				// Dynamic templates | ||||||
| 								new RawValue(StreamUtils.copyToString(mappings.getInputStream(), Charset.defaultCharset()))); | 				addDynamicTemplatesMapping(objectNode, entity); | ||||||
| 						return; | 
 | ||||||
| 					} | 				mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, | ||||||
|  | 						entity.findAnnotation(DynamicMapping.class), runtimeFields); | ||||||
|  | 
 | ||||||
|  | 				if (!excludeFromSource.isEmpty()) { | ||||||
|  | 					ObjectNode sourceNode = objectNode.putObject(SOURCE); | ||||||
|  | 					ArrayNode excludes = sourceNode.putArray(SOURCE_EXCLUDES); | ||||||
|  | 					excludeFromSource.stream().map(TextNode::new).forEach(excludes::add); | ||||||
| 				} | 				} | ||||||
| 			} else { | 
 | ||||||
| 				applyDisabledPropertyMapping(propertiesNode, property); | 				return objectMapper.writer().writeValueAsString(objectNode); | ||||||
| 				return; | 			} catch (IOException e) { | ||||||
|  | 				throw new MappingException("could not build mapping", e); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (property.isGeoPointProperty()) { | 		private void writeTypeHintMapping(ObjectNode propertiesNode) throws IOException { | ||||||
| 			applyGeoPointFieldMapping(propertiesNode, property); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		if (property.isGeoShapeProperty()) { | 			if (writeTypeHints) { | ||||||
| 			applyGeoShapeMapping(propertiesNode, property); | 				propertiesNode.set(TYPEHINT_PROPERTY, objectMapper.createObjectNode() // | ||||||
| 		} | 						.put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // | ||||||
| 
 | 						.put(FIELD_PARAM_INDEX, false) // | ||||||
| 		if (property.isJoinFieldProperty()) { | 						.put(FIELD_PARAM_DOC_VALUES, false)); | ||||||
| 			addJoinFieldMapping(propertiesNode, property); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		Field fieldAnnotation = property.findAnnotation(Field.class); |  | ||||||
| 		boolean isCompletionProperty = property.isCompletionProperty(); |  | ||||||
| 		boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property); |  | ||||||
| 		DynamicMapping dynamicMapping = property.findAnnotation(DynamicMapping.class); |  | ||||||
| 
 |  | ||||||
| 		if (!isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) { |  | ||||||
| 
 |  | ||||||
| 			if (fieldAnnotation == null) { |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			if (isNestedOrObjectProperty) { |  | ||||||
| 				Iterator<? extends TypeInformation<?>> iterator = property.getPersistentEntityTypes().iterator(); |  | ||||||
| 				ElasticsearchPersistentEntity<?> persistentEntity = iterator.hasNext() |  | ||||||
| 						? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next()) |  | ||||||
| 						: null; |  | ||||||
| 
 |  | ||||||
| 				mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(), |  | ||||||
| 						fieldAnnotation, dynamicMapping, null); |  | ||||||
| 				return; |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		MultiField multiField = property.findAnnotation(MultiField.class); | 		private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity, | ||||||
|  | 				boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, | ||||||
|  | 				@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping, | ||||||
|  | 				@Nullable Document runtimeFields) throws IOException { | ||||||
| 
 | 
 | ||||||
| 		if (isCompletionProperty) { | 			if (entity != null && entity.isAnnotationPresent(Mapping.class)) { | ||||||
| 			CompletionField completionField = property.findAnnotation(CompletionField.class); | 				Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class); | ||||||
| 			applyCompletionFieldMapping(propertiesNode, property, completionField); |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		if (isRootObject && fieldAnnotation != null && property.isIdProperty()) { | 				if (!mappingAnnotation.enabled()) { | ||||||
| 			applyDefaultIdFieldMapping(propertiesNode, property); | 					objectNode.put(MAPPING_ENABLED, false); | ||||||
| 		} else if (multiField != null) { | 					return; | ||||||
| 			addMultiFieldMapping(propertiesNode, property, multiField, isNestedOrObjectProperty, dynamicMapping); | 				} | ||||||
| 		} else if (fieldAnnotation != null) { |  | ||||||
| 			addSingleFieldMapping(propertiesNode, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private boolean hasRelevantAnnotation(ElasticsearchPersistentProperty property) { | 				if (mappingAnnotation.dateDetection() != Mapping.Detection.DEFAULT) { | ||||||
|  | 					objectNode.put(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name())); | ||||||
|  | 				} | ||||||
| 
 | 
 | ||||||
| 		return property.findAnnotation(Field.class) != null || property.findAnnotation(MultiField.class) != null | 				if (mappingAnnotation.numericDetection() != Mapping.Detection.DEFAULT) { | ||||||
| 				|| property.findAnnotation(GeoPointField.class) != null | 					objectNode.put(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name())); | ||||||
| 				|| property.findAnnotation(CompletionField.class) != null; | 				} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private void applyGeoPointFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) | 				if (mappingAnnotation.dynamicDateFormats().length > 0) { | ||||||
| 			throws IOException { | 					objectNode.putArray(DYNAMIC_DATE_FORMATS).addAll(Arrays.stream(mappingAnnotation.dynamicDateFormats()) | ||||||
| 		propertiesNode.set(property.getFieldName(), | 							.map(TextNode::valueOf).collect(Collectors.toList())); | ||||||
| 				objectMapper.createObjectNode().put(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT)); | 				} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private void applyGeoShapeMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) | 				if (runtimeFields != null) { | ||||||
| 			throws IOException { | 					objectNode.set(RUNTIME, objectMapper.convertValue(runtimeFields, JsonNode.class)); | ||||||
| 
 | 				} | ||||||
| 		ObjectNode shapeNode = propertiesNode.putObject(property.getFieldName()); |  | ||||||
| 		GeoShapeMappingParameters mappingParameters = GeoShapeMappingParameters |  | ||||||
| 				.from(property.findAnnotation(GeoShapeField.class)); |  | ||||||
| 		mappingParameters.writeTypeAndParametersTo(shapeNode); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private void applyCompletionFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property, |  | ||||||
| 			@Nullable CompletionField annotation) throws IOException { |  | ||||||
| 
 |  | ||||||
| 		ObjectNode completionNode = propertyNode.putObject(property.getFieldName()); |  | ||||||
| 		completionNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION); |  | ||||||
| 
 |  | ||||||
| 		if (annotation != null) { |  | ||||||
| 			completionNode.put(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength()); |  | ||||||
| 			completionNode.put(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements()); |  | ||||||
| 			completionNode.put(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators()); |  | ||||||
| 
 |  | ||||||
| 			if (StringUtils.hasLength(annotation.searchAnalyzer())) { |  | ||||||
| 				completionNode.put(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer()); |  | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if (StringUtils.hasLength(annotation.analyzer())) { | 			boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField); | ||||||
| 				completionNode.put(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer()); | 			if (writeNestedProperties) { | ||||||
|  | 
 | ||||||
|  | 				String type = nestedOrObjectField ? fieldType.toString().toLowerCase() | ||||||
|  | 						: FieldType.Object.toString().toLowerCase(); | ||||||
|  | 
 | ||||||
|  | 				ObjectNode nestedObjectNode = objectMapper.createObjectNode(); | ||||||
|  | 				nestedObjectNode.put(FIELD_PARAM_TYPE, type); | ||||||
|  | 
 | ||||||
|  | 				if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null | ||||||
|  | 						&& parentFieldAnnotation.includeInParent()) { | ||||||
|  | 					nestedObjectNode.put(FIELD_INCLUDE_IN_PARENT, true); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				objectNode.set(nestedObjectFieldName, nestedObjectNode); | ||||||
|  | 				// now go on with the nested one | ||||||
|  | 				objectNode = nestedObjectNode; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if (annotation.contexts().length > 0) { | 			if (entity != null && entity.dynamic() != Dynamic.INHERIT) { | ||||||
|  | 				objectNode.put(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase()); | ||||||
|  | 			} else if (dynamicMapping != null) { | ||||||
|  | 				objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 				ArrayNode contextsNode = completionNode.putArray(COMPLETION_CONTEXTS); | 			ObjectNode propertiesNode = objectNode.putObject(FIELD_PROPERTIES); | ||||||
| 				for (CompletionContext context : annotation.contexts()) { |  | ||||||
| 
 | 
 | ||||||
| 					ObjectNode contextNode = contextsNode.addObject(); | 			writeTypeHintMapping(propertiesNode); | ||||||
| 					contextNode.put(FIELD_CONTEXT_NAME, context.name()); |  | ||||||
| 					contextNode.put(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase()); |  | ||||||
| 
 | 
 | ||||||
| 					if (context.precision().length() > 0) { | 			if (entity != null) { | ||||||
| 						contextNode.put(FIELD_CONTEXT_PRECISION, context.precision()); | 				entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> { | ||||||
|  | 					try { | ||||||
|  | 						if (property.isAnnotationPresent(Transient.class) || isInIgnoreFields(property, parentFieldAnnotation)) { | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if (property.isSeqNoPrimaryTermProperty()) { | ||||||
|  | 							if (property.isAnnotationPresent(Field.class)) { | ||||||
|  | 								logger.warn("Property {} of {} is annotated for inclusion in mapping, but its type is " + // | ||||||
|  | 								"SeqNoPrimaryTerm that is never mapped, so it is skipped", // | ||||||
|  | 										property.getFieldName(), entity.getType()); | ||||||
|  | 							} | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						buildPropertyMapping(propertiesNode, isRootObject, property); | ||||||
|  | 					} catch (IOException e) { | ||||||
|  | 						logger.warn("error mapping property with name {}", property.getName(), e); | ||||||
| 					} | 					} | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 					if (StringUtils.hasText(context.path())) { | 		private void buildPropertyMapping(ObjectNode propertiesNode, boolean isRootObject, | ||||||
| 						contextNode.put(FIELD_CONTEXT_PATH, context.path()); | 				ElasticsearchPersistentProperty property) throws IOException { | ||||||
|  | 
 | ||||||
|  | 			if (property.isAnnotationPresent(Mapping.class)) { | ||||||
|  | 
 | ||||||
|  | 				Mapping mapping = property.getRequiredAnnotation(Mapping.class); | ||||||
|  | 
 | ||||||
|  | 				if (mapping.enabled()) { | ||||||
|  | 					String mappingPath = mapping.mappingPath(); | ||||||
|  | 
 | ||||||
|  | 					if (StringUtils.hasText(mappingPath)) { | ||||||
|  | 
 | ||||||
|  | 						ClassPathResource mappings = new ClassPathResource(mappingPath); | ||||||
|  | 						if (mappings.exists()) { | ||||||
|  | 							propertiesNode.putRawValue(property.getFieldName(), | ||||||
|  | 									new RawValue(StreamUtils.copyToString(mappings.getInputStream(), Charset.defaultCharset()))); | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					applyDisabledPropertyMapping(propertiesNode, property); | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (property.isGeoPointProperty()) { | ||||||
|  | 				applyGeoPointFieldMapping(propertiesNode, property); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (property.isGeoShapeProperty()) { | ||||||
|  | 				applyGeoShapeMapping(propertiesNode, property); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (property.isJoinFieldProperty()) { | ||||||
|  | 				addJoinFieldMapping(propertiesNode, property); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			String nestedPropertyPath = nestedPropertyPrefix.isEmpty() ? property.getFieldName() | ||||||
|  | 					: nestedPropertyPrefix + '.' + property.getFieldName(); | ||||||
|  | 
 | ||||||
|  | 			Field fieldAnnotation = property.findAnnotation(Field.class); | ||||||
|  | 
 | ||||||
|  | 			if (fieldAnnotation != null && fieldAnnotation.excludeFromSource()) { | ||||||
|  | 				excludeFromSource.add(nestedPropertyPath); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			boolean isCompletionProperty = property.isCompletionProperty(); | ||||||
|  | 			boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property); | ||||||
|  | 			DynamicMapping dynamicMapping = property.findAnnotation(DynamicMapping.class); | ||||||
|  | 
 | ||||||
|  | 			if (!isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) { | ||||||
|  | 
 | ||||||
|  | 				if (fieldAnnotation == null) { | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (isNestedOrObjectProperty) { | ||||||
|  | 					Iterator<? extends TypeInformation<?>> iterator = property.getPersistentEntityTypes().iterator(); | ||||||
|  | 					ElasticsearchPersistentEntity<?> persistentEntity = iterator.hasNext() | ||||||
|  | 							? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next()) | ||||||
|  | 							: null; | ||||||
|  | 
 | ||||||
|  | 					String currentNestedPropertyPrefix = nestedPropertyPrefix; | ||||||
|  | 					nestedPropertyPrefix = nestedPropertyPath; | ||||||
|  | 
 | ||||||
|  | 					mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(), | ||||||
|  | 							fieldAnnotation, dynamicMapping, null); | ||||||
|  | 
 | ||||||
|  | 					nestedPropertyPrefix = currentNestedPropertyPrefix; | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			MultiField multiField = property.findAnnotation(MultiField.class); | ||||||
|  | 
 | ||||||
|  | 			if (isCompletionProperty) { | ||||||
|  | 				CompletionField completionField = property.findAnnotation(CompletionField.class); | ||||||
|  | 				applyCompletionFieldMapping(propertiesNode, property, completionField); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (isRootObject && fieldAnnotation != null && property.isIdProperty()) { | ||||||
|  | 				applyDefaultIdFieldMapping(propertiesNode, property); | ||||||
|  | 			} else if (multiField != null) { | ||||||
|  | 				addMultiFieldMapping(propertiesNode, property, multiField, isNestedOrObjectProperty, dynamicMapping); | ||||||
|  | 			} else if (fieldAnnotation != null) { | ||||||
|  | 				addSingleFieldMapping(propertiesNode, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private boolean hasRelevantAnnotation(ElasticsearchPersistentProperty property) { | ||||||
|  | 
 | ||||||
|  | 			return property.findAnnotation(Field.class) != null || property.findAnnotation(MultiField.class) != null | ||||||
|  | 					|| property.findAnnotation(GeoPointField.class) != null | ||||||
|  | 					|| property.findAnnotation(CompletionField.class) != null; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void applyGeoPointFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) | ||||||
|  | 				throws IOException { | ||||||
|  | 			propertiesNode.set(property.getFieldName(), | ||||||
|  | 					objectMapper.createObjectNode().put(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void applyGeoShapeMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) | ||||||
|  | 				throws IOException { | ||||||
|  | 
 | ||||||
|  | 			ObjectNode shapeNode = propertiesNode.putObject(property.getFieldName()); | ||||||
|  | 			GeoShapeMappingParameters mappingParameters = GeoShapeMappingParameters | ||||||
|  | 					.from(property.findAnnotation(GeoShapeField.class)); | ||||||
|  | 			mappingParameters.writeTypeAndParametersTo(shapeNode); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void applyCompletionFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property, | ||||||
|  | 				@Nullable CompletionField annotation) throws IOException { | ||||||
|  | 
 | ||||||
|  | 			ObjectNode completionNode = propertyNode.putObject(property.getFieldName()); | ||||||
|  | 			completionNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION); | ||||||
|  | 
 | ||||||
|  | 			if (annotation != null) { | ||||||
|  | 				completionNode.put(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength()); | ||||||
|  | 				completionNode.put(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements()); | ||||||
|  | 				completionNode.put(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators()); | ||||||
|  | 
 | ||||||
|  | 				if (StringUtils.hasLength(annotation.searchAnalyzer())) { | ||||||
|  | 					completionNode.put(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer()); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (StringUtils.hasLength(annotation.analyzer())) { | ||||||
|  | 					completionNode.put(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer()); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if (annotation.contexts().length > 0) { | ||||||
|  | 
 | ||||||
|  | 					ArrayNode contextsNode = completionNode.putArray(COMPLETION_CONTEXTS); | ||||||
|  | 					for (CompletionContext context : annotation.contexts()) { | ||||||
|  | 
 | ||||||
|  | 						ObjectNode contextNode = contextsNode.addObject(); | ||||||
|  | 						contextNode.put(FIELD_CONTEXT_NAME, context.name()); | ||||||
|  | 						contextNode.put(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase()); | ||||||
|  | 
 | ||||||
|  | 						if (context.precision().length() > 0) { | ||||||
|  | 							contextNode.put(FIELD_CONTEXT_PRECISION, context.precision()); | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if (StringUtils.hasText(context.path())) { | ||||||
|  | 							contextNode.put(FIELD_CONTEXT_PATH, context.path()); | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private void applyDefaultIdFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property) | 		private void applyDefaultIdFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property) | ||||||
| 			throws IOException { | 				throws IOException { | ||||||
| 		propertyNode.set(property.getFieldName(), objectMapper.createObjectNode()// | 			propertyNode.set(property.getFieldName(), objectMapper.createObjectNode()// | ||||||
| 				.put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // | 					.put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // | ||||||
| 				.put(FIELD_INDEX, true) // | 					.put(FIELD_INDEX, true) // | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private void applyDisabledPropertyMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) { |  | ||||||
| 
 |  | ||||||
| 		try { |  | ||||||
| 			Field field = property.getRequiredAnnotation(Field.class); |  | ||||||
| 
 |  | ||||||
| 			if (field.type() != FieldType.Object) { |  | ||||||
| 				throw new IllegalArgumentException("Field type must be 'object"); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() // |  | ||||||
| 					.put(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) // |  | ||||||
| 					.put(MAPPING_ENABLED, false) // |  | ||||||
| 			); | 			); | ||||||
| 
 |  | ||||||
| 		} catch (Exception e) { |  | ||||||
| 			throw new MappingException("Could not write enabled: false mapping for " + property.getFieldName(), e); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Add mapping for @Field annotation |  | ||||||
| 	 * |  | ||||||
| 	 * @throws IOException |  | ||||||
| 	 */ |  | ||||||
| 	private void addSingleFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property, |  | ||||||
| 			Field annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException { |  | ||||||
| 
 |  | ||||||
| 		// build the property json, if empty skip it as this is no valid mapping |  | ||||||
| 		ObjectNode fieldNode = objectMapper.createObjectNode(); |  | ||||||
| 		addFieldMappingParameters(fieldNode, annotation, nestedOrObjectField); |  | ||||||
| 
 |  | ||||||
| 		if (fieldNode.isEmpty()) { |  | ||||||
| 			return; |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		propertiesNode.set(property.getFieldName(), fieldNode); | 		private void applyDisabledPropertyMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) { | ||||||
| 
 | 
 | ||||||
| 		if (nestedOrObjectField) { | 			try { | ||||||
| 			if (annotation.dynamic() != Dynamic.INHERIT) { | 				Field field = property.getRequiredAnnotation(Field.class); | ||||||
| 				fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase()); |  | ||||||
| 			} else if (dynamicMapping != null) { |  | ||||||
| 				fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private void addJoinFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) | 				if (field.type() != FieldType.Object) { | ||||||
| 			throws IOException { | 					throw new IllegalArgumentException("Field type must be 'object"); | ||||||
| 		JoinTypeRelation[] joinTypeRelations = property.getRequiredAnnotation(JoinTypeRelations.class).relations(); | 				} | ||||||
| 
 | 
 | ||||||
| 		if (joinTypeRelations.length == 0) { | 				propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() // | ||||||
| 			logger.warn("Property {}s type is JoinField but its annotation JoinTypeRelation is " + // | 						.put(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) // | ||||||
| 					"not properly maintained", // | 						.put(MAPPING_ENABLED, false) // | ||||||
| 					property.getFieldName()); | 				); | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		ObjectNode propertyNode = propertiesNode.putObject(property.getFieldName()); | 			} catch (Exception e) { | ||||||
| 		propertyNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_JOIN); | 				throw new MappingException("Could not write enabled: false mapping for " + property.getFieldName(), e); | ||||||
| 
 |  | ||||||
| 		ObjectNode relationsNode = propertyNode.putObject(JOIN_TYPE_RELATIONS); |  | ||||||
| 
 |  | ||||||
| 		for (JoinTypeRelation joinTypeRelation : joinTypeRelations) { |  | ||||||
| 			String parent = joinTypeRelation.parent(); |  | ||||||
| 			String[] children = joinTypeRelation.children(); |  | ||||||
| 
 |  | ||||||
| 			if (children.length > 1) { |  | ||||||
| 				relationsNode.putArray(parent) |  | ||||||
| 						.addAll(Arrays.stream(children).map(TextNode::valueOf).collect(Collectors.toList())); |  | ||||||
| 			} else if (children.length == 1) { |  | ||||||
| 				relationsNode.put(parent, children[0]); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Add mapping for @MultiField annotation |  | ||||||
| 	 * |  | ||||||
| 	 * @throws IOException |  | ||||||
| 	 */ |  | ||||||
| 	private void addMultiFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property, |  | ||||||
| 			MultiField annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException { |  | ||||||
| 
 |  | ||||||
| 		// main field |  | ||||||
| 		ObjectNode mainFieldNode = objectMapper.createObjectNode(); |  | ||||||
| 		propertyNode.set(property.getFieldName(), mainFieldNode); |  | ||||||
| 
 |  | ||||||
| 		if (nestedOrObjectField) { |  | ||||||
| 			if (annotation.mainField().dynamic() != Dynamic.INHERIT) { |  | ||||||
| 				mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase()); |  | ||||||
| 			} else if (dynamicMapping != null) { |  | ||||||
| 				mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		addFieldMappingParameters(mainFieldNode, annotation.mainField(), nestedOrObjectField); | 		/** | ||||||
|  | 		 * Add mapping for @Field annotation | ||||||
|  | 		 * | ||||||
|  | 		 * @throws IOException | ||||||
|  | 		 */ | ||||||
|  | 		private void addSingleFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property, | ||||||
|  | 				Field annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException { | ||||||
| 
 | 
 | ||||||
| 		// inner fields | 			// build the property json, if empty skip it as this is no valid mapping | ||||||
| 		ObjectNode innerFieldsNode = mainFieldNode.putObject("fields"); | 			ObjectNode fieldNode = objectMapper.createObjectNode(); | ||||||
|  | 			addFieldMappingParameters(fieldNode, annotation, nestedOrObjectField); | ||||||
| 
 | 
 | ||||||
| 		for (InnerField innerField : annotation.otherFields()) { | 			if (fieldNode.isEmpty()) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 			ObjectNode innerFieldNode = innerFieldsNode.putObject(innerField.suffix()); | 			propertiesNode.set(property.getFieldName(), fieldNode); | ||||||
| 			addFieldMappingParameters(innerFieldNode, innerField, false); |  | ||||||
| 
 | 
 | ||||||
|  | 			if (nestedOrObjectField) { | ||||||
|  | 				if (annotation.dynamic() != Dynamic.INHERIT) { | ||||||
|  | 					fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase()); | ||||||
|  | 				} else if (dynamicMapping != null) { | ||||||
|  | 					fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private void addFieldMappingParameters(ObjectNode fieldNode, Annotation annotation, boolean nestedOrObjectField) | 		private void addJoinFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) | ||||||
| 			throws IOException { | 				throws IOException { | ||||||
|  | 			JoinTypeRelation[] joinTypeRelations = property.getRequiredAnnotation(JoinTypeRelations.class).relations(); | ||||||
| 
 | 
 | ||||||
| 		MappingParameters mappingParameters = MappingParameters.from(annotation); | 			if (joinTypeRelations.length == 0) { | ||||||
|  | 				logger.warn("Property {}s type is JoinField but its annotation JoinTypeRelation is " + // | ||||||
|  | 						"not properly maintained", // | ||||||
|  | 						property.getFieldName()); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 		if (!nestedOrObjectField && mappingParameters.isStore()) { | 			ObjectNode propertyNode = propertiesNode.putObject(property.getFieldName()); | ||||||
| 			fieldNode.put(FIELD_PARAM_STORE, true); | 			propertyNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_JOIN); | ||||||
|  | 
 | ||||||
|  | 			ObjectNode relationsNode = propertyNode.putObject(JOIN_TYPE_RELATIONS); | ||||||
|  | 
 | ||||||
|  | 			for (JoinTypeRelation joinTypeRelation : joinTypeRelations) { | ||||||
|  | 				String parent = joinTypeRelation.parent(); | ||||||
|  | 				String[] children = joinTypeRelation.children(); | ||||||
|  | 
 | ||||||
|  | 				if (children.length > 1) { | ||||||
|  | 					relationsNode.putArray(parent) | ||||||
|  | 							.addAll(Arrays.stream(children).map(TextNode::valueOf).collect(Collectors.toList())); | ||||||
|  | 				} else if (children.length == 1) { | ||||||
|  | 					relationsNode.put(parent, children[0]); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		mappingParameters.writeTypeAndParametersTo(fieldNode); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	/** | 		/** | ||||||
| 	 * Apply mapping for dynamic templates. | 		 * Add mapping for @MultiField annotation | ||||||
| 	 * | 		 * | ||||||
| 	 * @throws IOException | 		 * @throws IOException | ||||||
| 	 */ | 		 */ | ||||||
| 	private void addDynamicTemplatesMapping(ObjectNode objectNode, ElasticsearchPersistentEntity<?> entity) | 		private void addMultiFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property, | ||||||
| 			throws IOException { | 				MultiField annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) | ||||||
|  | 				throws IOException { | ||||||
| 
 | 
 | ||||||
| 		if (entity.isAnnotationPresent(DynamicTemplates.class)) { | 			// main field | ||||||
| 			String mappingPath = entity.getRequiredAnnotation(DynamicTemplates.class).mappingPath(); | 			ObjectNode mainFieldNode = objectMapper.createObjectNode(); | ||||||
| 			if (hasText(mappingPath)) { | 			propertyNode.set(property.getFieldName(), mainFieldNode); | ||||||
| 
 | 
 | ||||||
| 				String jsonString = ResourceUtil.readFileFromClasspath(mappingPath); | 			if (nestedOrObjectField) { | ||||||
| 				if (hasText(jsonString)) { | 				if (annotation.mainField().dynamic() != Dynamic.INHERIT) { | ||||||
|  | 					mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase()); | ||||||
|  | 				} else if (dynamicMapping != null) { | ||||||
|  | 					mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
| 
 | 
 | ||||||
| 					JsonNode jsonNode = objectMapper.readTree(jsonString).get("dynamic_templates"); | 			addFieldMappingParameters(mainFieldNode, annotation.mainField(), nestedOrObjectField); | ||||||
| 					if (jsonNode != null && jsonNode.isArray()) { | 
 | ||||||
| 						objectNode.set(FIELD_DYNAMIC_TEMPLATES, jsonNode); | 			// inner fields | ||||||
|  | 			ObjectNode innerFieldsNode = mainFieldNode.putObject("fields"); | ||||||
|  | 
 | ||||||
|  | 			for (InnerField innerField : annotation.otherFields()) { | ||||||
|  | 
 | ||||||
|  | 				ObjectNode innerFieldNode = innerFieldsNode.putObject(innerField.suffix()); | ||||||
|  | 				addFieldMappingParameters(innerFieldNode, innerField, false); | ||||||
|  | 
 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void addFieldMappingParameters(ObjectNode fieldNode, Annotation annotation, boolean nestedOrObjectField) | ||||||
|  | 				throws IOException { | ||||||
|  | 
 | ||||||
|  | 			MappingParameters mappingParameters = MappingParameters.from(annotation); | ||||||
|  | 
 | ||||||
|  | 			if (!nestedOrObjectField && mappingParameters.isStore()) { | ||||||
|  | 				fieldNode.put(FIELD_PARAM_STORE, true); | ||||||
|  | 			} | ||||||
|  | 			mappingParameters.writeTypeAndParametersTo(fieldNode); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		/** | ||||||
|  | 		 * Apply mapping for dynamic templates. | ||||||
|  | 		 * | ||||||
|  | 		 * @throws IOException | ||||||
|  | 		 */ | ||||||
|  | 		private void addDynamicTemplatesMapping(ObjectNode objectNode, ElasticsearchPersistentEntity<?> entity) | ||||||
|  | 				throws IOException { | ||||||
|  | 
 | ||||||
|  | 			if (entity.isAnnotationPresent(DynamicTemplates.class)) { | ||||||
|  | 				String mappingPath = entity.getRequiredAnnotation(DynamicTemplates.class).mappingPath(); | ||||||
|  | 				if (hasText(mappingPath)) { | ||||||
|  | 
 | ||||||
|  | 					String jsonString = ResourceUtil.readFileFromClasspath(mappingPath); | ||||||
|  | 					if (hasText(jsonString)) { | ||||||
|  | 
 | ||||||
|  | 						JsonNode jsonNode = objectMapper.readTree(jsonString).get("dynamic_templates"); | ||||||
|  | 						if (jsonNode != null && jsonNode.isArray()) { | ||||||
|  | 							objectNode.set(FIELD_DYNAMIC_TEMPLATES, jsonNode); | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private boolean isAnyPropertyAnnotatedWithField(@Nullable ElasticsearchPersistentEntity entity) { | 		private boolean isAnyPropertyAnnotatedWithField(@Nullable ElasticsearchPersistentEntity entity) { | ||||||
| 
 | 
 | ||||||
| 		return entity != null && entity.getPersistentProperty(Field.class) != null; | 			return entity != null && entity.getPersistentProperty(Field.class) != null; | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	private boolean isInIgnoreFields(ElasticsearchPersistentProperty property, @Nullable Field parentFieldAnnotation) { |  | ||||||
| 
 |  | ||||||
| 		if (null != parentFieldAnnotation) { |  | ||||||
| 
 |  | ||||||
| 			String[] ignoreFields = parentFieldAnnotation.ignoreFields(); |  | ||||||
| 			return Arrays.asList(ignoreFields).contains(property.getFieldName()); |  | ||||||
| 		} | 		} | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	private boolean isNestedOrObjectProperty(ElasticsearchPersistentProperty property) { | 		private boolean isInIgnoreFields(ElasticsearchPersistentProperty property, @Nullable Field parentFieldAnnotation) { | ||||||
| 
 | 
 | ||||||
| 		Field fieldAnnotation = property.findAnnotation(Field.class); | 			if (null != parentFieldAnnotation) { | ||||||
| 		return fieldAnnotation != null | 
 | ||||||
| 				&& (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type()); | 				String[] ignoreFields = parentFieldAnnotation.ignoreFields(); | ||||||
|  | 				return Arrays.asList(ignoreFields).contains(property.getFieldName()); | ||||||
|  | 			} | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private boolean isNestedOrObjectProperty(ElasticsearchPersistentProperty property) { | ||||||
|  | 
 | ||||||
|  | 			Field fieldAnnotation = property.findAnnotation(Field.class); | ||||||
|  | 			return fieldAnnotation != null | ||||||
|  | 					&& (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type()); | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ import java.lang.Integer; | |||||||
| import java.lang.Object; | import java.lang.Object; | ||||||
| import java.math.BigDecimal; | import java.math.BigDecimal; | ||||||
| import java.time.Instant; | import java.time.Instant; | ||||||
|  | import java.time.LocalDate; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| @ -47,13 +48,13 @@ import org.springframework.data.elasticsearch.core.ElasticsearchOperations; | |||||||
| import org.springframework.data.elasticsearch.core.IndexOperations; | import org.springframework.data.elasticsearch.core.IndexOperations; | ||||||
| import org.springframework.data.elasticsearch.core.MappingContextBaseTests; | import org.springframework.data.elasticsearch.core.MappingContextBaseTests; | ||||||
| import org.springframework.data.elasticsearch.core.SearchHits; | import org.springframework.data.elasticsearch.core.SearchHits; | ||||||
| import org.springframework.data.elasticsearch.core.suggest.Completion; |  | ||||||
| import org.springframework.data.elasticsearch.core.geo.GeoPoint; | import org.springframework.data.elasticsearch.core.geo.GeoPoint; | ||||||
| import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; | import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; | ||||||
| import org.springframework.data.elasticsearch.core.query.IndexQuery; | import org.springframework.data.elasticsearch.core.query.IndexQuery; | ||||||
| import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; | import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; | ||||||
| import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; | import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; | ||||||
| import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; | import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; | ||||||
|  | import org.springframework.data.elasticsearch.core.suggest.Completion; | ||||||
| import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; | import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; | ||||||
| import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; | import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; | ||||||
| import org.springframework.data.geo.Box; | import org.springframework.data.geo.Box; | ||||||
| @ -325,6 +326,16 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { | |||||||
| 
 | 
 | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test // #796 | ||||||
|  | 	@DisplayName("should write source excludes") | ||||||
|  | 	void shouldWriteSourceExcludes() { | ||||||
|  | 
 | ||||||
|  | 		IndexOperations indexOps = operations.indexOps(ExcludedFieldEntity.class); | ||||||
|  | 		indexOps.create(); | ||||||
|  | 		indexOps.putMapping(); | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// region entities | 	// region entities | ||||||
| 	@Document(indexName = "ignore-above-index") | 	@Document(indexName = "ignore-above-index") | ||||||
| 	static class IgnoreAboveEntity { | 	static class IgnoreAboveEntity { | ||||||
| @ -1172,6 +1183,18 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { | |||||||
| 		@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp; | 		@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Document(indexName = "fields-excluded-from-source") | ||||||
|  | 	private static class ExcludedFieldEntity { | ||||||
|  | 		@Id @Nullable private String id; | ||||||
|  | 		@Nullable @Field(name = "excluded-date", type = Date, format = DateFormat.date, | ||||||
|  | 				excludeFromSource = true) private LocalDate excludedDate; | ||||||
|  | 		@Nullable @Field(type = Nested) private NestedExcludedFieldEntity nestedEntity; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private static class NestedExcludedFieldEntity { | ||||||
|  | 		@Nullable @Field(name = "excluded-text", type = Text, excludeFromSource = true) private String excludedText; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// endregion | 	// endregion | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -42,10 +42,10 @@ import org.springframework.data.annotation.Id; | |||||||
| import org.springframework.data.annotation.Transient; | import org.springframework.data.annotation.Transient; | ||||||
| import org.springframework.data.elasticsearch.annotations.*; | import org.springframework.data.elasticsearch.annotations.*; | ||||||
| import org.springframework.data.elasticsearch.core.MappingContextBaseTests; | import org.springframework.data.elasticsearch.core.MappingContextBaseTests; | ||||||
| import org.springframework.data.elasticsearch.core.suggest.Completion; |  | ||||||
| import org.springframework.data.elasticsearch.core.geo.GeoPoint; | import org.springframework.data.elasticsearch.core.geo.GeoPoint; | ||||||
| import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; | import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; | ||||||
| import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; | import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; | ||||||
|  | import org.springframework.data.elasticsearch.core.suggest.Completion; | ||||||
| import org.springframework.data.geo.Box; | import org.springframework.data.geo.Box; | ||||||
| import org.springframework.data.geo.Circle; | import org.springframework.data.geo.Circle; | ||||||
| import org.springframework.data.geo.Point; | import org.springframework.data.geo.Point; | ||||||
| @ -945,6 +945,49 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { | |||||||
| 
 | 
 | ||||||
| 		assertEquals(expected, mapping, true); | 		assertEquals(expected, mapping, true); | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test // #796 | ||||||
|  | 	@DisplayName("should add fields that are excluded from source") | ||||||
|  | 	void shouldAddFieldsThatAreExcludedFromSource() throws JSONException { | ||||||
|  | 
 | ||||||
|  | 		String expected = "{\n" + // | ||||||
|  | 				"  \"properties\": {\n" + // | ||||||
|  | 				"    \"_class\": {\n" + // | ||||||
|  | 				"      \"type\": \"keyword\",\n" + // | ||||||
|  | 				"      \"index\": false,\n" + // | ||||||
|  | 				"      \"doc_values\": false\n" + // | ||||||
|  | 				"    },\n" + // | ||||||
|  | 				"    \"excluded-date\": {\n" + // | ||||||
|  | 				"      \"type\": \"date\",\n" + // | ||||||
|  | 				"      \"format\": \"date\"\n" + // | ||||||
|  | 				"    },\n" + // | ||||||
|  | 				"    \"nestedEntity\": {\n" + // | ||||||
|  | 				"      \"type\": \"nested\",\n" + // | ||||||
|  | 				"      \"properties\": {\n" + // | ||||||
|  | 				"        \"_class\": {\n" + // | ||||||
|  | 				"          \"type\": \"keyword\",\n" + // | ||||||
|  | 				"          \"index\": false,\n" + // | ||||||
|  | 				"          \"doc_values\": false\n" + // | ||||||
|  | 				"        },\n" + // | ||||||
|  | 				"        \"excluded-text\": {\n" + // | ||||||
|  | 				"          \"type\": \"text\"\n" + // | ||||||
|  | 				"        }\n" + // | ||||||
|  | 				"      }\n" + // | ||||||
|  | 				"    }\n" + // | ||||||
|  | 				"  },\n" + // | ||||||
|  | 				"  \"_source\": {\n" + // | ||||||
|  | 				"    \"excludes\": [\n" + // | ||||||
|  | 				"      \"excluded-date\",\n" + // | ||||||
|  | 				"      \"nestedEntity.excluded-text\"\n" + // | ||||||
|  | 				"    ]\n" + // | ||||||
|  | 				"  }\n" + // | ||||||
|  | 				"}\n"; // | ||||||
|  | 
 | ||||||
|  | 		String mapping = getMappingBuilder().buildPropertyMapping(ExcludedFieldEntity.class); | ||||||
|  | 
 | ||||||
|  | 		assertEquals(expected, mapping, true); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// region entities | 	// region entities | ||||||
| 
 | 
 | ||||||
| 	@Document(indexName = "ignore-above-index") | 	@Document(indexName = "ignore-above-index") | ||||||
| @ -1918,5 +1961,17 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { | |||||||
| 		@Id @Nullable private String id; | 		@Id @Nullable private String id; | ||||||
| 		@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp; | 		@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	@Document(indexName = "fields-excluded-from-source") | ||||||
|  | 	private static class ExcludedFieldEntity { | ||||||
|  | 		@Id @Nullable private String id; | ||||||
|  | 		@Nullable @Field(name = "excluded-date", type = Date, format = DateFormat.date, | ||||||
|  | 				excludeFromSource = true) private LocalDate excludedDate; | ||||||
|  | 		@Nullable @Field(type = Nested) private NestedExcludedFieldEntity nestedEntity; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private static class NestedExcludedFieldEntity { | ||||||
|  | 		@Nullable @Field(name = "excluded-text", type = Text, excludeFromSource = true) private String excludedText; | ||||||
|  | 	} | ||||||
| 	// endregion | 	// endregion | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user