com.haulmont.restapi.config.RestJsonTransformations.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.restapi.config.RestJsonTransformations.java

Source

/*
 * Copyright (c) 2008-2017 Haulmont.
 *
 * 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 com.haulmont.restapi.config;

import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Table;
import com.haulmont.bali.util.Dom4j;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.Resources;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.restapi.transform.EntityJsonTransformer;
import com.haulmont.restapi.transform.JsonTransformationDirection;
import com.haulmont.restapi.transform.StandardEntityJsonTransformer;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.text.StrTokenizer;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Class is used for loading and storing JSON transformers. These transformers are used when a request to the REST API
 * is made by the client that works with a data model of the previous version. In this case transformer transforms a
 * JSON from the old data model version to the current or vice versa. JSON transformers are loaded from configuration
 * files defined by the {@code cuba.rest.jsonTransformationConfig} application property.
 */
@Component("cuba_RestJsonTransformations")
public class RestJsonTransformations {

    protected final String CUBA_REST_JSON_TRANSFORMATION_CONFIG_PROP_NAME = "cuba.rest.jsonTransformationConfig";

    private static final Logger log = LoggerFactory.getLogger(RestJsonTransformations.class);

    protected volatile boolean initialized;

    protected ReadWriteLock lock = new ReentrantReadWriteLock();

    protected Table<String, String, EntityJsonTransformer> toVersionTransformers = HashBasedTable.create();

    protected Table<String, String, EntityJsonTransformer> fromVersionTransformers = HashBasedTable.create();

    @Inject
    protected Resources resources;

    //inject the context here because we can't use the AppContext for getting beans. REST API has its own spring context
    @Inject
    protected WebApplicationContext restApiContext;

    /**
     * Tries to find the transformer for the given entity, model version and transformation direction. If such
     * transformer is defined in the transformers configuration file then it will be returned. If there is no
     * transformer defined, a dummy transformer that doesn't do any transformation will be returned.
     *
     * @param entityName           entity name
     * @param modelVersion         domain model version
     * @param transformerDirection transformation direction (to version or from version)
     * @return a JSON transformer instance
     */
    public EntityJsonTransformer getTransformer(String entityName, String modelVersion,
            JsonTransformationDirection transformerDirection) {
        lock.readLock().lock();
        try {
            checkInitialized();
            EntityJsonTransformer transformer;
            switch (transformerDirection) {
            case TO_VERSION:
                transformer = toVersionTransformers.get(entityName, modelVersion);
                break;
            case FROM_VERSION:
                transformer = fromVersionTransformers.get(entityName, modelVersion);
                break;
            default:
                throw new IllegalArgumentException("Unknown transformerType: " + transformerDirection);
            }

            if (transformer == null) {
                //create a transformer that doesn't do transformation to current entity attributes, but processes nested entities
                transformer = restApiContext.getBean(StandardEntityJsonTransformer.class, entityName, entityName,
                        modelVersion, transformerDirection);
            }

            return transformer;
        } finally {
            lock.readLock().unlock();
        }
    }

    protected void checkInitialized() {
        if (!initialized) {
            lock.readLock().unlock();
            lock.writeLock().lock();
            try {
                if (!initialized) {
                    init();
                    initialized = true;
                }
            } finally {
                lock.readLock().lock();
                lock.writeLock().unlock();
            }
        }
    }

    protected void init() {
        String configName = AppContext.getProperty(CUBA_REST_JSON_TRANSFORMATION_CONFIG_PROP_NAME);
        StrTokenizer tokenizer = new StrTokenizer(configName);
        for (String location : tokenizer.getTokenArray()) {
            Resource resource = resources.getResource(location);
            if (resource.exists()) {
                InputStream stream = null;
                try {
                    stream = resource.getInputStream();
                    loadConfig(Dom4j.readDocument(stream).getRootElement());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } finally {
                    IOUtils.closeQuietly(stream);
                }
            } else {
                log.warn("Resource " + location + " not found, ignore it");
            }
        }
    }

    protected void loadConfig(Element rootElem) {
        for (Element transformationElem : Dom4j.elements(rootElem, "transformation")) {
            String modelVersion = transformationElem.attributeValue("modelVersion");
            if (Strings.isNullOrEmpty(modelVersion)) {
                log.error("modelVersion attribute is not defined");
                continue;
            }
            String currentEntityName = transformationElem.attributeValue("currentEntityName");
            if (Strings.isNullOrEmpty(currentEntityName)) {
                log.error("currentEntityName attribute is not defined");
                continue;
            }

            String oldEntityName = transformationElem.attributeValue("oldEntityName");
            if (Strings.isNullOrEmpty(oldEntityName)) {
                oldEntityName = currentEntityName;
            }

            Element customElem = transformationElem.element("custom");

            if (customElem != null) {
                parseCustomTransformers(currentEntityName, oldEntityName, modelVersion, customElem);
            } else {
                parseStandardTransformers(currentEntityName, oldEntityName, modelVersion, transformationElem);
            }
        }
    }

    protected void parseStandardTransformers(String currentEntityName, String oldEntityName, String version,
            Element transformationElem) {
        BiMap<String, String> renamedAttributesMap = HashBiMap.create();

        for (Element renameAttributeElem : Dom4j.elements(transformationElem, "renameAttribute")) {
            String oldAttributeName = renameAttributeElem.attributeValue("oldName");
            if (Strings.isNullOrEmpty(oldAttributeName)) {
                log.error("oldName attribute for renamed attribute is not defined");
                continue;
            }

            String currentAttributeName = renameAttributeElem.attributeValue("currentName");
            if (Strings.isNullOrEmpty(currentAttributeName)) {
                log.error("currentName attribute for renamed attribute is not defined");
                continue;
            }
            renamedAttributesMap.put(oldAttributeName, currentAttributeName);
        }

        StandardEntityJsonTransformer fromVersionTransformer = restApiContext.getBean(
                StandardEntityJsonTransformer.class, oldEntityName, currentEntityName, version,
                JsonTransformationDirection.FROM_VERSION);
        fromVersionTransformer.setAttributesToRename(renamedAttributesMap);

        StandardEntityJsonTransformer toVersionTransformer = restApiContext.getBean(
                StandardEntityJsonTransformer.class, currentEntityName, oldEntityName, version,
                JsonTransformationDirection.TO_VERSION);
        toVersionTransformer.setAttributesToRename(renamedAttributesMap.inverse());

        Element fromVersionElem = transformationElem.element("fromVersion");
        if (fromVersionElem != null) {
            processOneDirectionConfig(fromVersionElem, fromVersionTransformer);
        }

        Element toVersionElem = transformationElem.element("toVersion");
        if (toVersionElem != null) {
            processOneDirectionConfig(toVersionElem, toVersionTransformer);
        }

        toVersionTransformers.put(currentEntityName, version, toVersionTransformer);
        fromVersionTransformers.put(oldEntityName, version, fromVersionTransformer);
    }

    protected void parseCustomTransformers(String currentEntityName, String oldEntityName, String version,
            Element customElem) {
        Element fromVersionElem = customElem.element("fromVersion");
        if (fromVersionElem != null) {
            String transformerBeanName = fromVersionElem.attributeValue("transformerBeanRef");
            try {
                Object transformer = restApiContext.getBean(transformerBeanName);
                if (transformer instanceof EntityJsonTransformer) {
                    fromVersionTransformers.put(oldEntityName, version, (EntityJsonTransformer) transformer);
                } else {
                    log.error("Custom transformer {} doesn't implement EntityJsonTransformer", transformerBeanName);
                }
            } catch (NoSuchBeanDefinitionException e) {
                log.error("Bean {} for custom transformer not found", transformerBeanName);
            }
        }

        Element toVersionElem = customElem.element("toVersion");
        if (toVersionElem != null) {
            String transformerBeanName = toVersionElem.attributeValue("transformerBeanRef");
            try {
                Object transformer = restApiContext.getBean(transformerBeanName);
                if (transformer instanceof EntityJsonTransformer) {
                    toVersionTransformers.put(currentEntityName, version, (EntityJsonTransformer) transformer);
                } else {
                    log.error("Custom transformer {} doesn't implement EntityJsonTransformer", transformerBeanName);
                }
            } catch (NoSuchBeanDefinitionException e) {
                log.error("Bean {} for custom transformer not found", transformerBeanName);
            }
        }
    }

    protected void processOneDirectionConfig(Element element, StandardEntityJsonTransformer transformer) {
        Set<String> removeAttributes = new HashSet<>();
        for (Element removeAttributeElem : Dom4j.elements(element, "removeAttribute")) {
            String name = removeAttributeElem.attributeValue("name");
            if (Strings.isNullOrEmpty(name)) {
                log.error("name for removeAttribute element is not specified");
                continue;
            }
            removeAttributes.add(name);
        }
        transformer.setAttributesToRemove(removeAttributes);
    }
}