All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sdl.dxa.tridion.mapping.impl.DefaultModelBuilder Maven / Gradle / Ivy

package com.sdl.dxa.tridion.mapping.impl;

import com.google.common.base.Strings;
import com.sdl.dxa.api.datamodel.model.BinaryContentData;
import com.sdl.dxa.api.datamodel.model.EntityModelData;
import com.sdl.dxa.api.datamodel.model.ExternalContentData;
import com.sdl.dxa.api.datamodel.model.MvcModelData;
import com.sdl.dxa.api.datamodel.model.PageModelData;
import com.sdl.dxa.api.datamodel.model.RegionModelData;
import com.sdl.dxa.api.datamodel.model.ViewModelData;
import com.sdl.dxa.api.datamodel.model.util.ListWrapper;
import com.sdl.dxa.tridion.mapping.EntityModelBuilder;
import com.sdl.dxa.tridion.mapping.ModelBuilderPipeline;
import com.sdl.dxa.tridion.mapping.PageModelBuilder;
import com.sdl.webapp.common.api.WebRequestContext;
import com.sdl.webapp.common.api.localization.Localization;
import com.sdl.webapp.common.api.mapping.semantic.SemanticMapper;
import com.sdl.webapp.common.api.mapping.semantic.SemanticMappingException;
import com.sdl.webapp.common.api.mapping.semantic.config.FieldSemantics;
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.AbstractViewModel;
import com.sdl.webapp.common.api.model.EntityModel;
import com.sdl.webapp.common.api.model.MvcData;
import com.sdl.webapp.common.api.model.PageModel;
import com.sdl.webapp.common.api.model.RegionModel;
import com.sdl.webapp.common.api.model.RegionModelSet;
import com.sdl.webapp.common.api.model.ViewModel;
import com.sdl.webapp.common.api.model.ViewModelRegistry;
import com.sdl.webapp.common.api.model.entity.AbstractEntityModel;
import com.sdl.webapp.common.api.model.entity.EclItem;
import com.sdl.webapp.common.api.model.entity.ExceptionEntity;
import com.sdl.webapp.common.api.model.entity.MediaItem;
import com.sdl.webapp.common.api.model.mvcdata.DefaultsMvcData;
import com.sdl.webapp.common.api.model.mvcdata.MvcDataCreator;
import com.sdl.webapp.common.api.model.mvcdata.MvcDataImpl;
import com.sdl.webapp.common.api.model.page.DefaultPageModel;
import com.sdl.webapp.common.exceptions.DxaException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Default implementation of {@link EntityModelBuilder} and {@link PageModelBuilder}. Priority of this builder is always {@code highest precedence}.
 *
 */
@Slf4j
@Service
public class DefaultModelBuilder implements EntityModelBuilder, PageModelBuilder {

    @Autowired
    private ViewModelRegistry viewModelRegistry;

    @Autowired
    private SemanticMapper semanticMapper;

    @Autowired
    private ModelBuilderPipeline modelBuilderPipeline;

    @Autowired
    private WebRequestContext webRequestContext;

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }

    /**
     * {@inheritDoc}
     *
     */
    @Override
    public  T buildEntityModel(@Nullable T originalEntityModel,
                                                      @NotNull EntityModelData modelData,
                                                      @Nullable Class expectedClass) throws DxaException {
        try {
            MvcData mvcData = null;
            Class modelType;
            if (expectedClass != null) {
                log.debug("Expected class is pre-set to {} for model {}", expectedClass, modelData);
                // https://jira.sdl.com/browse/TSI-2273
                // we currently ignore the base type because of the issue but don't ignore the fact that it's set
                SemanticSchema semanticSchema = webRequestContext.getLocalization().getSemanticSchemas().get(Long.parseLong(modelData.getSchemaId()));
                modelType = viewModelRegistry.getMappedModelTypes(semanticSchema.getFullyQualifiedNames(), expectedClass);
            } else {
                mvcData = createMvcData(modelData.getMvcData(), DefaultsMvcData.ENTITY);
                log.debug("Expected class is not set explicitly, trying to get it from MvcData");
                modelType = viewModelRegistry.getViewModelType(mvcData);
            }
            if (modelType == null) throw new IllegalStateException("Could not determine ModelType " +
                    (expectedClass != null
                            ? " from pre-selected class " + expectedClass.getCanonicalName()
                            : " from MvcData class " + mvcData.getClass().getCanonicalName()));

            T entityModel = (T) createViewModel(modelType, modelData);
            entityModel.setMvcData(mvcData);

            ((AbstractEntityModel) entityModel).setId(modelData.getId());
            fillViewModel(entityModel, modelData);

            processMediaItem(modelData, entityModel);
            return entityModel;
        } catch (ReflectiveOperationException | DxaException e) {
            throw new DxaException("Exception happened while creating a entity model from: " + modelData, e);
        }
    }

    @NotNull
    MvcData createMvcData(MvcModelData modelData, DefaultsMvcData defaults) {
        if (modelData == null) {
            return MvcDataCreator.creator(MvcDataImpl.newBuilder()).defaults(defaults).create();
        }
        return MvcDataCreator.creator(MvcDataImpl.newBuilder()
                .actionName(modelData.getActionName())
                .areaName(modelData.getAreaName())
                .controllerAreaName(modelData.getControllerAreaName())
                .controllerName(modelData.getControllerName())
                .viewName(modelData.getViewName())
                .routeValues(modelData.getParameters())
        ).defaults(defaults).create();
    }

    @NotNull
     T createViewModel(Class viewModelType, @NonNull ViewModelData viewModelData)
            throws SemanticMappingException, ReflectiveOperationException {
        Localization localization = webRequestContext.getLocalization();
        List allSchemas = getInheritedSemanticSchemas(viewModelData, localization);
        SemanticSchema semanticSchema = null;
        if (!Strings.isNullOrEmpty(viewModelData.getSchemaId())) {
            long schemaId = Long.parseLong(viewModelData.getSchemaId());
            semanticSchema = localization.getSemanticSchemas().get(schemaId);
        } else {
            if (allSchemas.isEmpty()) {
                return viewModelType.newInstance();
            }
            semanticSchema = allSchemas.get(0);
        }
        Map semanticFields = getAllSemanticFields(semanticSchema, viewModelData);
        DefaultSemanticFieldDataProvider dataProvider = DefaultSemanticFieldDataProvider.getFor(viewModelData, semanticSchema);
        return semanticMapper.createEntity(viewModelType, semanticFields, dataProvider);
    }

    protected List getInheritedSemanticSchemas(ViewModelData viewModelData, Localization localization) {
        Object schemas = viewModelData.getExtensionData() != null ? viewModelData.getExtensionData().get("Schemas") : null;
        if (!(schemas instanceof ListWrapper) ||
            ((ListWrapper) schemas).getValues().isEmpty()) {
            return Collections.emptyList();
        }
        ListWrapper allInheritedSchemas = (ListWrapper) schemas;
        return allInheritedSchemas.getValues()
                .stream()
                .map(schemaId -> localization.getSemanticSchemas().get(Long.parseLong(schemaId)))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    @NotNull
    private Map getAllSemanticFields(@Nullable SemanticSchema semanticSchema,
                                                                    @NotNull ViewModelData modelData) {
        if (semanticSchema == null) {
            return Collections.emptyMap();
        }
        final Map semanticFields = semanticSchema.getSemanticFields();

        if (modelData.getExtensionData() == null) {
            return semanticFields;
        }
        Object schemas = modelData.getExtensionData().get("Schemas");
        if (!(schemas instanceof ListWrapper) ||
            ((ListWrapper) schemas).getValues().isEmpty()) {
            return semanticFields;
        }
        if (log.isDebugEnabled()) log.debug("Found additional semantic schemas {} used in the view model {}", schemas, modelData);

        Localization localization = webRequestContext.getLocalization();
        Map allAncestorsSemanticFields = new HashMap<>(semanticFields);

                //noinspection unchecked
        ListWrapper inheritedSchemas = (ListWrapper) schemas;
        allAncestorsSemanticFields.putAll(inheritedSchemas.getValues()
                .stream()
                        .map(schemaId -> localization.getSemanticSchemas().get(Long.parseLong(schemaId)))
                        .filter(Objects::nonNull)
                        .map(SemanticSchema::getSemanticFields)
                        .flatMap(fieldMap -> fieldMap.entrySet().stream())
                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));

        return allAncestorsSemanticFields;
    }

    void fillViewModel(@NotNull ViewModel viewModel, @NotNull ViewModelData modelData) {
        if (modelData.getExtensionData() != null) {
            modelData.getExtensionData().forEach(viewModel::addExtensionData);
        }

        if (viewModel instanceof AbstractViewModel && modelData.getXpmMetadata() != null) {
            ((AbstractViewModel) viewModel).setXpmMetadata(modelData.getXpmMetadata());
        }

        viewModel.setHtmlClasses(modelData.getHtmlClasses());
    }

    private  void processMediaItem(EntityModelData modelData, T entityModel) throws DxaException {
        if (!(entityModel instanceof MediaItem)) {
            log.debug("Entity model " + entityModel.getClass().getCanonicalName() +
                      " is not a MediaItem, processing as media will be skipped");
            return;
        }
        MediaItem mediaItem = (MediaItem) entityModel;
        BinaryContentData binaryContent = modelData.getBinaryContent();
        if (binaryContent == null) {
            throw new DxaException("Unable to create Media Item ('" + mediaItem.getClass() + "') " +
                    "because the Data Model '" + mediaItem.getId() + "') \"' does not contain Binary Content Data.");
        }
        mediaItem.setUrl(binaryContent.getUrl());
        mediaItem.setFileName(binaryContent.getFileName());
        mediaItem.setMimeType(binaryContent.getMimeType());
        mediaItem.setFileSize(binaryContent.getFileSize());

        if (mediaItem instanceof EclItem) {
            EclItem eclItem = (EclItem) mediaItem;

            ExternalContentData externalContent = modelData.getExternalContent();
            if (externalContent == null) {
                throw new DxaException("Unable to create ECL Item ('" + eclItem.getClass() + "') " +
                        "because the Data Model '" + eclItem.getId() + "') \"' does not contain External Content Data.");
            }

            eclItem.setDisplayTypeId(externalContent.getDisplayTypeId());
            eclItem.setTemplateFragment(externalContent.getTemplateFragment());
            eclItem.setExternalMetadata(externalContent.getMetadata());
            eclItem.setUri(externalContent.getId());
        }
    }

    /**
     * {@inheritDoc}
     *
     */
    @Override
    @NotNull
    public PageModel buildPageModel(@Nullable PageModel originalPageModel, @NotNull PageModelData modelData) throws SemanticMappingException {
        PageModel pageModel = instantiatePageModel(originalPageModel, modelData);

        webRequestContext.setPageContextId(modelData.getId());

        fillViewModel(pageModel, modelData);
        pageModel.setId(modelData.getId());
        pageModel.setMeta(modelData.getMeta());
        pageModel.setName(modelData.getTitle());
        pageModel.setTitle(getPageTitle(modelData));
        pageModel.setUrl(modelData.getUrlPath());
        processRegions(modelData.getRegions(), pageModel.getRegions());
        return pageModel;
    }

    void processRegions(List regions,
                        RegionModelSet regionsToAdd) throws SemanticMappingException {
        if (regions == null) {
            return;
        }
        for (RegionModelData region : regions) {
            RegionModel regionModel = createRegionModel(region);
            regionsToAdd.add(regionModel);
        }
    }

    @NotNull
    PageModel instantiatePageModel(@Nullable PageModel originalPageModel, @NotNull PageModelData pageModelData) throws SemanticMappingException {
        if (originalPageModel != null) {
            log.warn("Original page model is expected to be null but it's '{}'", originalPageModel);
            return originalPageModel;
        }
        MvcData mvcData = createMvcData(pageModelData.getMvcData(), DefaultsMvcData.PAGE);
        log.debug("MvcData '{}' for PageModel {}", mvcData, pageModelData);
        PageModel pageModel = null;
        try {
            Class viewModelType = viewModelRegistry.getViewModelType(mvcData);
            log.debug("Instantiating a PageModel without a SchemaID = null, modelData = {}, view model type = '{}'", pageModelData, viewModelType);
            // semantic mapping is possible, let's do it
            if (pageModelData.getSchemaId() == null) {
                pageModel = viewModelType == null ? createDefaultPageModel() : null;
            }
            if (pageModel == null) {
                pageModel = (PageModel) createViewModel(viewModelType, pageModelData);
            }
            pageModel.setMvcData(mvcData);
            return pageModel;
        } catch (ReflectiveOperationException | DxaException e) {
            throw new SemanticMappingException("Exception happened while creating a page model " + pageModelData.getId(), e);
        }
    }

    @NotNull
    DefaultPageModel createDefaultPageModel() {
        return new DefaultPageModel();
    }

    private String getPageTitle(PageModelData modelData) {
        Localization localization = webRequestContext.getLocalization();
        String title = "defaultPageTitle".equals(modelData.getTitle()) ? localization.getResource("core.defaultPageTitle") : modelData.getTitle();
        String separator = localization.getResource("core.pageTitleSeparator");
        String postfix = localization.getResource("core.pageTitlePostfix");
        log.trace("Model page title '{}', pageTitleSeparator '{}', postfix '{}'", title, separator, postfix);
        return title + (separator == null ? "" : separator) + (postfix == null ? "" :postfix);
    }

    RegionModel createRegionModel(RegionModelData regionModelData) throws SemanticMappingException {
        MvcData mvcData = createMvcData(regionModelData.getMvcData(), DefaultsMvcData.REGION);
        log.debug("MvcData '{}' for RegionModel {}", mvcData, regionModelData);

        try {
            Class viewModelType = viewModelRegistry.getViewModelType(mvcData);
            if (viewModelType == null) {
                throw new IllegalStateException("Cannot find a view model type for " + mvcData);
            }

            RegionModel regionModel = (RegionModel) createRegionModel(regionModelData, viewModelType);
            String schemaId = regionModelData.getSchemaId();
            regionModel.setSchemaId(schemaId);

            if (schemaId != null && !schemaId.isEmpty()){
                processOwnSchema(regionModelData, viewModelType, regionModel, schemaId);
            }

            fillViewModel(regionModel, regionModelData);
            regionModel.setMvcData(mvcData);

            processRegions(regionModelData.getRegions(), regionModel.getRegions());
            addEntitiesToRegionModels(regionModelData, regionModel);

            return regionModel;
        } catch (ReflectiveOperationException | DxaException e) {
            throw new SemanticMappingException("Exception happened while creating Region " + regionModelData, e);
        }
    }

    void addEntitiesToRegionModels(RegionModelData regionModelData, RegionModel regionModel) {
        if (regionModelData.getEntities() == null) {
            return;
        }
        regionModelData.getEntities().stream()
                .map(entityModelData -> {
                    EntityModel entityModel = createEntityModel(entityModelData);
                    MvcDataImpl.MvcDataImplBuilder creator = MvcDataCreator.creator(entityModel.getMvcData()).builder().regionName(regionModelData.getName());
                    entityModel.setMvcData(creator.build());
                    return entityModel;
                }).forEach(regionModel::addEntity);
    }

    void processOwnSchema(RegionModelData regionModelData, Class viewModelType, RegionModel regionModel, String schemaId) {
        Localization localization = webRequestContext.getLocalization();
        SemanticSchema semanticSchema = localization.getSemanticSchemas().get(Long.parseLong(schemaId));

        List allSchemas = semanticSchema == null ? getInheritedSemanticSchemas(regionModelData, localization) : Collections.emptyList();
        semanticSchema = semanticSchema == null && !allSchemas.isEmpty()
                ? allSchemas.get(0)
                : semanticSchema;
        Map semanticFields = getAllSemanticFields(semanticSchema, regionModelData);

        semanticMapper.mapSemanticFields(viewModelType,
                semanticFields,
                DefaultSemanticFieldDataProvider.getFor(regionModelData, semanticSchema),
                regionModel);
    }

    ViewModel createRegionModel(RegionModelData regionModelData, Class viewModelType) throws ReflectiveOperationException {
        Constructor constructorExists = null;
        for (Constructor constructor : viewModelType.getDeclaredConstructors()){
            if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == String.class) {
                constructorExists = (Constructor)constructor;
            }
        }
        if (constructorExists == null) {
            throw new IllegalStateException("ViewModel implementor class (" + viewModelType.getCanonicalName() + ") should have had constructor with single String 'name' argument");
        }
        return constructorExists.newInstance(regionModelData.getName());
    }

    @NotNull
    EntityModel createEntityModel(EntityModelData entityModelData) {
        try {
            return modelBuilderPipeline.createEntityModel(entityModelData);
        } catch (Exception e) {
            String message = "Cannot create an entity model for model data " + entityModelData;
            log.error(message, e);
            return new ExceptionEntity(e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy