org.dd4t.databind.builder.json.JsonDataBinder.java Source code

Java tutorial

Introduction

Here is the source code for org.dd4t.databind.builder.json.JsonDataBinder.java

Source

/*
 * Copyright (c) 2015 Radagio
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.dd4t.databind.builder.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import org.apache.commons.lang3.StringUtils;
import org.dd4t.contentmodel.*;
import org.dd4t.core.databind.BaseViewModel;
import org.dd4t.core.databind.DataBinder;
import org.dd4t.core.databind.TridionViewModel;
import org.dd4t.core.exceptions.SerializationException;
import org.dd4t.core.util.TCMURI;
import org.dd4t.databind.DataBindFactory;
import org.dd4t.databind.builder.BaseDataBinder;
import org.dd4t.databind.serializers.json.BaseFieldMixIn;
import org.dd4t.databind.serializers.json.ComponentPresentationDeserializer;
import org.dd4t.databind.util.DataBindConstants;
import org.dd4t.databind.util.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;

/**
 * @author R. Kempees
 * @since 17/11/14.
 */
public class JsonDataBinder extends BaseDataBinder implements DataBinder {
    private static final Logger LOG = LoggerFactory.getLogger(JsonDataBinder.class);
    private static final JsonDataBinder INSTANCE = new JsonDataBinder();
    private static final ObjectMapper GENERIC_MAPPER = new ObjectMapper();

    static {
        GENERIC_MAPPER.registerModule(new JodaModule());
        GENERIC_MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    private JsonDataBinder() {
        LoggerFactory.getLogger(JsonDataBinder.class).info("Creating a JsonDataBinder instance.");
    }

    public static JsonDataBinder getInstance() {
        return INSTANCE;
    }

    public <T extends Page> T buildPage(final String source, final Class<T> aClass) throws SerializationException {
        try {
            return GENERIC_MAPPER.readValue(source, aClass);
        } catch (IOException e) {
            LOG.error(DataBindConstants.MESSAGE_ERROR_DESERIALIZING, e);
            throw new SerializationException(e);
        }
    }

    public ComponentPresentation buildDynamicComponentPresentation(
            final ComponentPresentation componentPresentation, final Class<? extends Component> aClass)
            throws SerializationException {
        final Set<String> modelNames = new HashSet<>();
        try {
            String viewModelName = DataBindFactory
                    .findComponentTemplateViewName(componentPresentation.getComponentTemplate());
            final Component component = DataBindFactory
                    .buildComponent(componentPresentation.getRawComponentContent(), aClass);
            componentPresentation.setComponent(component);
            String rootElementName = component.getSchema().getRootElement();

            if (StringUtils.isEmpty(viewModelName)) {
                LOG.error("Viewmodel name not found on CT: {}. Not proceeding to build models",
                        componentPresentation.getComponentTemplate().getId());
                return componentPresentation;
            }

            modelNames.add(viewModelName);
            if (!rootElementName.equals(viewModelName)) {
                modelNames.add(rootElementName);
            }
            final JsonNode rawComponentData = GENERIC_MAPPER
                    .readTree(componentPresentation.getRawComponentContent());
            final Map<String, BaseViewModel> models = DataBindFactory.buildModels(rawComponentData, modelNames,
                    componentPresentation.getComponentTemplate().getId());

            componentPresentation.setViewModel(models);
        } catch (SerializationException | IOException e) {
            LOG.error(e.getLocalizedMessage(), e);
        }
        return componentPresentation;
    }

    public <T extends Component> T buildComponent(final Object source, final Class<T> aClass)
            throws SerializationException {
        try {
            if (source instanceof JsonNode) {
                final JsonParser parser = ((JsonNode) source).traverse();
                return GENERIC_MAPPER.readValue(parser, aClass);
            } else if (source instanceof String) {
                return GENERIC_MAPPER.readValue((String) source, aClass);
            } else {
                LOG.error("Cannot parse type: " + source.getClass().toString());
                return null;
            }

        } catch (IOException e) {
            LOG.error(DataBindConstants.MESSAGE_ERROR_DESERIALIZING, e);
            throw new SerializationException(e);
        }
    }

    public Map<String, BaseViewModel> buildModels(final Object source, final Set<String> modelNames,
            final String templateUri) throws SerializationException {

        final Map<String, BaseViewModel> models = new HashMap<>();

        for (String modelName : modelNames) {
            if (VIEW_MODELS.containsKey(modelName)) {
                final Class modelClass = VIEW_MODELS.get(modelName);
                // check to ensure we don't already have built the same model. We can reuse it if the case
                // this loop is cheaper than deserializing new models all the time
                final BaseViewModel alreadyExistingModel = getModelOrNullForExistingEntry(models, modelClass);
                if (alreadyExistingModel != null) {
                    models.put(modelName, alreadyExistingModel);
                } else {
                    models.put(modelName, buildModel(source, modelClass, templateUri));
                }
            } else {
                LOG.warn("Could not load Model Class for key: {}", modelName);
            }
        }
        return models;
    }

    /**
     * @param source      the source object. In this case a JsonNode
     * @param modelName   the viewModel name set on the CT
     * @param templateUri the CT URI. Just to set it on the model
     * @param <T>         Any mode deriving from BaseViewModel
     * @return A concrete instance of the view model.
     * @throws SerializationException
     */
    public <T extends BaseViewModel> T buildModel(final Object source, final String modelName,
            final String templateUri) throws SerializationException {
        if (VIEW_MODELS.containsKey(modelName)) {
            Class modelClass = VIEW_MODELS.get(modelName);
            LOG.info("Start building model for viewName: {}, with class: {}", modelName, modelClass);
            return buildModel(source, modelClass, templateUri);
        }
        LOG.info("Could not load Model Class for viewName: {}", modelName);
        return null;
    }

    public <T extends BaseViewModel> T buildModel(final Object source, final Class modelClass,
            final String templateUri) throws SerializationException {

        try {
            // TODO:If anyone can resolve this unchecked cast, then great.
            // This appears a limitation in the Java Generics implementation.
            final T concreteModel = (T) modelClass.newInstance();

            if (concreteModel instanceof TridionViewModel && !StringUtils.isEmpty(templateUri)) {
                ((TridionViewModel) concreteModel).setTemplateUri(new TCMURI(templateUri));
            }

            LOG.debug("Building model {}", concreteModel.getClass().toString());
            return this.converter.convertSource(source, concreteModel);
        } catch (InstantiationException | IllegalAccessException | ParseException e) {
            LOG.error(e.getLocalizedMessage(), e);
        }
        return null;
    }

    public static ObjectMapper getGenericMapper() {
        return GENERIC_MAPPER;
    }

    @PostConstruct
    @Override
    protected void init() {

        this.configureMapper();
        this.checkViewModelConfiguration();
        this.scanAndLoadModels();
    }

    protected void configureMapper() {
        // This is the hook where view models are custom generated
        final ComponentPresentationDeserializer componentPresentationDeserializer = new ComponentPresentationDeserializer(
                this.concreteComponentPresentationImpl, this.concreteComponentTemplateImpl,
                this.concreteComponentImpl);
        final SimpleModule module = new SimpleModule("ComponentPresentationDeserializerModule",
                new Version(1, 0, 0, "RELEASE", "org.dd4t", "dd4t-databind"));
        module.addDeserializer(ComponentPresentation.class, componentPresentationDeserializer);
        GENERIC_MAPPER.registerModule(module);
        GENERIC_MAPPER.addMixInAnnotations(Field.class, BaseFieldMixIn.class);

        LOG.debug("Mapper configured for: {} and {}", this.concreteComponentPresentationImpl.toString(),
                this.concreteComponentTemplateImpl.toString());
    }

    public String findComponentTemplateViewName(ComponentTemplate template) throws IOException {
        if (template == null) {
            throw new IOException("The component template to find the viewModel of is null.");
        }

        final Map<String, Field> metaData = template.getMetadata();
        if (metaData != null && metaData.containsKey(JsonDataBinder.getInstance().viewModelMetaKeyName)) {

            Field viewNameField = metaData.get(JsonDataBinder.getInstance().viewModelMetaKeyName);
            if (viewNameField != null) {
                List<Object> values = viewNameField.getValues();
                if (!values.isEmpty()) {
                    return (String) viewNameField.getValues().get(0);
                }
            }
        }
        return null;
    }

    public String getRootElementName(Object componentNode) {

        if (!JsonUtils.isValidJsonNode(componentNode)) {
            LOG.error("Dunno what you're trying to do, but we're doing Json here.");
            return null;
        }

        final JsonNode node = (JsonNode) componentNode;
        final JsonNode schemaNode = node.get(DataBindConstants.SCHEMA_NODE_NAME);
        if (schemaNode != null) {
            String nodeTypeName;
            if (schemaNode.hasNonNull(DataBindConstants.ROOT_ELEMENT_NAME)) {
                nodeTypeName = schemaNode.get(DataBindConstants.ROOT_ELEMENT_NAME).textValue();
                LOG.debug("RootElementName is: {}", nodeTypeName);
                return nodeTypeName;
            }
        }
        return null;
    }
}