Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sdl.dxa.tridion.mapping.impl.DefaultSemanticFieldDataProvider Maven / Gradle / Ivy
package com.sdl.dxa.tridion.mapping.impl;
import com.sdl.dxa.api.datamodel.model.ContentModelData;
import com.sdl.dxa.api.datamodel.model.EntityModelData;
import com.sdl.dxa.api.datamodel.model.KeywordModelData;
import com.sdl.dxa.api.datamodel.model.ViewModelData;
import com.sdl.dxa.api.datamodel.model.util.CanWrapContentAndMetadata;
import com.sdl.dxa.api.datamodel.model.util.ListWrapper;
import com.sdl.dxa.api.datamodel.model.util.ModelDataWrapper;
import com.sdl.dxa.tridion.mapping.ModelBuilderPipeline;
import com.sdl.dxa.tridion.mapping.converter.GenericSemanticModelDataConverter;
import com.sdl.webapp.common.api.WebRequestContext;
import com.sdl.webapp.common.api.mapping.semantic.FieldData;
import com.sdl.webapp.common.api.mapping.semantic.SemanticFieldDataProvider;
import com.sdl.webapp.common.api.mapping.semantic.SemanticMappingException;
import com.sdl.webapp.common.api.mapping.semantic.config.FieldPath;
import com.sdl.webapp.common.api.mapping.semantic.config.SemanticField;
import com.sdl.webapp.common.api.mapping.semantic.config.SemanticSchema;
import com.sdl.webapp.common.api.model.KeywordModel;
import com.sdl.webapp.common.api.model.entity.Link;
import com.sdl.webapp.common.api.model.entity.MediaItem;
import com.sdl.webapp.common.exceptions.DxaException;
import com.sdl.webapp.common.util.TcmUtils;
import com.sdl.webapp.tridion.fields.exceptions.FieldConverterException;
import com.sdl.webapp.tridion.fields.exceptions.UnsupportedTargetTypeException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.sdl.webapp.common.util.ApplicationContextHolder.getContext;
@Slf4j
public class DefaultSemanticFieldDataProvider implements SemanticFieldDataProvider {
private ModelDataWrapper dataWrapper;
@Getter
private SemanticSchema semanticSchema;
private GenericSemanticModelDataConverter genericSemanticModelDataConverter;
private ModelBuilderPipeline pipeline;
private int embeddingLevel = 0;
private int iteration = 0;
private String context = null;
private DefaultSemanticFieldDataProvider(ModelDataWrapper dataWrapper, SemanticSchema semanticSchema) {
this.dataWrapper = dataWrapper;
this.semanticSchema = semanticSchema;
this.genericSemanticModelDataConverter = getContext().getBean(GenericSemanticModelDataConverter.class);
this.pipeline = getContext().getBean(ModelBuilderPipeline.class);
}
@Nullable
public static DefaultSemanticFieldDataProvider getFor(ViewModelData model, SemanticSchema semanticSchema) {
return getFor((Object) model, semanticSchema);
}
@Nullable
private static DefaultSemanticFieldDataProvider getFor(@NotNull Object model, SemanticSchema semanticSchema) {
if (!(model instanceof CanWrapContentAndMetadata)) {
log.debug("Type {} is not supported by embedded SemanticFieldDataProvider", model.getClass());
return null;
}
return new DefaultSemanticFieldDataProvider(((CanWrapContentAndMetadata) model).getDataWrapper(), semanticSchema);
}
/**
* Shifts the context of this data provider to the given index for the given mode. It is used to iterate over lists of values and to save XPath context.
*
* @param model current item in the list
* @param semanticField current parent semantic field inside which we iterate
* @param index current item index in a list
* @return data provider shifted to the given index
*/
@Nullable
public DefaultSemanticFieldDataProvider iteration(Object model, SemanticField semanticField, int index) {
DefaultSemanticFieldDataProvider provider = getFor(model, semanticSchema);
if (provider != null) {
provider.embeddingLevel = this.embeddingLevel;
provider.iteration = index + 1;
provider.context = semanticField.getXPath(this.context) + "[" + provider.iteration + "]";
log.trace("Iterating over some list, current index = {}, item = {}, embedding level = {}, context = {}",
index, model, this.embeddingLevel, provider.context);
}
return provider;
}
/**
* Increases the embedding level of the data provider saving the current XPath context. It is used to go inside the nested maps.
*
* @param value current value in the map
* @return data provider embedded to the next elvel
*/
@Nullable
public DefaultSemanticFieldDataProvider embedded(Object value) {
DefaultSemanticFieldDataProvider provider = getFor(value, this.semanticSchema);
if (provider != null) {
provider.iteration = this.iteration;
provider.context = this.context;
provider.embeddingLevel = this.embeddingLevel + 1;
log.trace("Embedding into embedding level = {}, index = {}, item = {}, context = {}",
provider.iteration, value, this.embeddingLevel, provider.context);
}
return provider;
}
@Override
public FieldData getFieldData(SemanticField semanticField, TypeDescriptor targetType) throws SemanticMappingException {
log.trace("semanticField: {}, targetType: {}", semanticField, targetType);
ContentModelData data = semanticField.getPath().isMetadata() ? dataWrapper.getMetadata() : dataWrapper.getContent();
FieldPath path = getCurrentPath(semanticField);
if (path == null) {
log.warn("Path is null for semantic field {}, embedding level {}", semanticField, embeddingLevel);
return null;
}
Optional field = findField(data, path);
if (!field.isPresent()) {
log.debug("No data is found for path {} for semantic field {}", path, semanticField);
return null;
}
Object value = genericSemanticModelDataConverter.convert(field.get(), targetType, semanticField, pipeline, this);
return new FieldData(value, semanticField.getXPath(this.context));
}
@Override
public Object getSelfFieldData(TypeDescriptor targetType) throws SemanticMappingException {
Class> objectType = targetType.getObjectType();
if (MediaItem.class.isAssignableFrom(objectType) || Link.class.isAssignableFrom(objectType) || String.class.isAssignableFrom(objectType)) {
try {
return genericSemanticModelDataConverter.selfLink(dataWrapper.getWrappedModel(), targetType, pipeline);
} catch (DxaException e) {
throw new SemanticMappingException("Failed self-linking " + targetType, e);
}
}
throw new UnsupportedTargetTypeException(targetType);
}
@Override
public Map getAllFieldData(Class targetType) throws SemanticMappingException {
final Map fieldData = new HashMap<>();
Stream> content = getEntryStream(dataWrapper.getContent());
Stream> metadata = getEntryStream(dataWrapper.getMetadata());
for (Map.Entry entry : Stream.concat(content, metadata).collect(Collectors.toSet()))
{
this.getAllFieldsData(entry, fieldData, targetType);
}
return fieldData;
}
private Stream> getEntryStream(ContentModelData contentModelData) {
return contentModelData != null ? contentModelData.entrySet().stream() : Stream.empty();
}
private void getAllFieldsData(Map.Entry entry, Map fieldData ,Class targetType) throws FieldConverterException {
if ("settings".equals(entry.getKey())) {
throw new UnsupportedOperationException("'settings' field handling"); //todo dxa2 implement
}
Object value = entry.getValue();
if (!fieldData.containsKey(entry.getKey()) && value != null)
{
if (targetType == KeywordModel.class )
{
if (value instanceof KeywordModelData)
{
T keyword = (T) genericSemanticModelDataConverter.convert(value, TypeDescriptor.valueOf(KeywordModel.class), null, pipeline, this);
fieldData.put(entry.getKey(), keyword);
}
}
else if(targetType == String.class)
{
Optional emdTcmUri = (Optional) getEntityModelDataTcmUriOrNull(value);
fieldData.put(entry.getKey(), emdTcmUri.orElse(
(T) genericSemanticModelDataConverter.convert(value, TypeDescriptor.valueOf(String.class), null,
pipeline, this)));
}
}
}
@NotNull
private Optional getEntityModelDataTcmUriOrNull(Object possibleEmd) {
EntityModelData modelData = null;
if (possibleEmd instanceof EntityModelData) {
modelData = (EntityModelData) possibleEmd;
} else if (possibleEmd instanceof ListWrapper.EntityModelDataListWrapper) {
modelData = ((ListWrapper.EntityModelDataListWrapper) possibleEmd).get(0);
}
if (modelData == null) {
return Optional.empty();
}
String localizationId = getContext().getBean(WebRequestContext.class).getLocalization().getId();
return Optional.of(TcmUtils.buildTcmUri(localizationId, modelData.getId()));
}
@Nullable
private FieldPath getCurrentPath(SemanticField semanticField) {
FieldPath path = semanticField.getPath().getTail();
for (int i = 0; i < embeddingLevel && path != null; i++) {
path = path.getTail();
}
return path;
}
@NotNull
private Optional findField(final ContentModelData data, FieldPath fieldPath) {
if (data == null || data.isEmpty() || fieldPath == null) {
return Optional.empty();
}
Object field = data.get(fieldPath.getHead());
if (fieldPath.hasTail()) {
if (field instanceof ListWrapper) {
field = ((ListWrapper) field).get(0);
log.debug("Field with path {} has multiple values, getting first {}", fieldPath, field);
}
if (field instanceof String && StringUtils.isEmpty((String) field)) {
//Special case where all fields of ContentModelData are empty.
//In this case it gets converted to an empty string ("")
return Optional.empty();
}
return findField((ContentModelData) field, fieldPath.getTail());
}
return Optional.ofNullable(field);
}
}