com.haulmont.cuba.core.sys.MetadataBuildSupport.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.core.sys.MetadataBuildSupport.java

Source

/*
 * Copyright (c) 2008-2016 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.cuba.core.sys;

import com.haulmont.bali.util.Dom4j;
import com.haulmont.bali.util.ReflectionHelper;
import com.haulmont.chile.core.datatypes.DatatypeRegistry;
import com.haulmont.cuba.core.global.Resources;
import com.haulmont.cuba.core.global.Stores;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrTokenizer;
import org.dom4j.Document;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Pattern;

@Component("cuba_MetadataBuildSupport")
public class MetadataBuildSupport {

    public static class XmlAnnotation {
        public final Object value;
        public final Map<String, Object> attributes = new HashMap<>();

        public XmlAnnotation(Object value) {
            this.value = value;
        }
    }

    public static class XmlAnnotations {
        public final String entityClass;
        public final Map<String, XmlAnnotation> annotations = new HashMap<>();
        public final List<XmlAnnotations> attributeAnnotations = new ArrayList<>();

        public XmlAnnotations(String entityClass) {
            this.entityClass = entityClass;
        }
    }

    public static class XmlFile {
        public final String name;
        public final Element root;

        public XmlFile(String name, Element root) {
            this.name = name;
            this.root = root;
        }
    }

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

    public static final String PERSISTENCE_CONFIG = "cuba.persistenceConfig";
    public static final String METADATA_CONFIG = "cuba.metadataConfig";

    @Inject
    protected Resources resources;

    @Inject
    protected DatatypeRegistry datatypes;

    private static final Pattern JAVA_CLASS_PATTERN = Pattern
            .compile("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*");

    public List<XmlFile> init() {
        List<XmlFile> metadataXmlList = new ArrayList<>();
        StrTokenizer metadataFilesTokenizer = new StrTokenizer(getMetadataConfig());
        for (String fileName : metadataFilesTokenizer.getTokenArray()) {
            metadataXmlList.add(new XmlFile(fileName, readXml(fileName)));
        }
        return metadataXmlList;
    }

    /**
     * @param storeName data store name
     * @return location of persistent entities descriptor or null if not defined
     */
    @Nullable
    public String getPersistenceConfig(String storeName) {
        String propName = PERSISTENCE_CONFIG;
        if (!Stores.isMain(storeName))
            propName = propName + "_" + storeName;

        String config = AppContext.getProperty(propName);
        if (StringUtils.isBlank(config)) {
            log.trace("Property {} is not set, assuming {} is not a RdbmsStore", propName, storeName);
            return null;
        } else
            return config;
    }

    /**
     * @return location of metadata descriptor
     */
    public String getMetadataConfig() {
        String config = AppContext.getProperty(METADATA_CONFIG);
        if (StringUtils.isBlank(config))
            throw new IllegalStateException(METADATA_CONFIG + " application property is not defined");
        return config;
    }

    public List<Element> getDatatypeElements(List<XmlFile> metadataXmlList) {
        List<Element> list = new ArrayList<>();
        for (XmlFile xmlFile : metadataXmlList) {
            Element datatypesEl = xmlFile.root.element("datatypes");
            if (datatypesEl != null) {
                list.addAll(Dom4j.elements(datatypesEl, "datatype"));
            }
        }
        return list;
    }

    public Map<String, List<EntityClassInfo>> getEntityPackages(List<XmlFile> metadataXmlList) {
        Map<String, List<EntityClassInfo>> packages = new LinkedHashMap<>();

        loadFromMetadataConfig(packages, metadataXmlList);
        Stores.getAll().forEach(db -> loadFromPersistenceConfig(packages, db));

        return packages;
    }

    protected void loadFromMetadataConfig(Map<String, List<EntityClassInfo>> packages,
            List<XmlFile> metadataXmlList) {
        for (XmlFile xmlFile : metadataXmlList) {
            for (Element element : Dom4j.elements(xmlFile.root, "metadata-model")) {
                String rootPackage = element.attributeValue("root-package");
                if (StringUtils.isBlank(rootPackage))
                    throw new IllegalStateException("metadata-model/@root-package is empty in " + xmlFile.name);

                List<EntityClassInfo> classNames = packages.get(rootPackage);
                if (classNames == null) {
                    classNames = new ArrayList<>();
                    packages.put(rootPackage, classNames);
                }
                for (Element classEl : Dom4j.elements(element, "class")) {
                    classNames.add(
                            new EntityClassInfo(classEl.attributeValue("store"), classEl.getText().trim(), false));
                }
            }
        }
    }

    protected void loadFromPersistenceConfig(Map<String, List<EntityClassInfo>> packages, String db) {
        String persistenceConfig = getPersistenceConfig(db);
        if (persistenceConfig == null)
            return;
        StrTokenizer persistenceFilesTokenizer = new StrTokenizer(persistenceConfig);
        for (String fileName : persistenceFilesTokenizer.getTokenArray()) {
            Element root = readXml(fileName);
            Element puEl = root.element("persistence-unit");
            if (puEl == null)
                throw new IllegalStateException("File " + fileName + " has no persistence-unit element");

            for (Element classEl : Dom4j.elements(puEl, "class")) {
                String className = classEl.getText().trim();
                boolean included = false;
                for (String rootPackage : packages.keySet()) {
                    if (className.startsWith(rootPackage + ".")) {
                        List<EntityClassInfo> classNames = packages.get(rootPackage);
                        if (classNames == null) {
                            classNames = new ArrayList<>();
                            packages.put(rootPackage, classNames);
                        }
                        classNames.add(new EntityClassInfo(db, className, true));
                        included = true;
                        break;
                    }
                }
                if (!included)
                    throw new IllegalStateException("Can not find a model for class " + className
                            + ". The class's package must be inside of some model's root package.");
            }
        }
    }

    protected Element readXml(String path) {
        InputStream stream = resources.getResourceAsStream(path);
        try {
            stream = resources.getResourceAsStream(path);
            if (stream == null)
                throw new IllegalStateException("Resource not found: " + path);
            Document document = Dom4j.readDocument(stream);
            return document.getRootElement();
        } finally {
            IOUtils.closeQuietly(stream);
        }
    }

    public List<XmlAnnotations> getEntityAnnotations(List<XmlFile> metadataXmlList) {
        List<XmlAnnotations> result = new ArrayList<>();

        for (XmlFile xmlFile : metadataXmlList) {
            Element annotationsEl = xmlFile.root.element("annotations");
            if (annotationsEl != null) {
                for (Element entityEl : Dom4j.elements(annotationsEl, "entity")) {
                    String className = entityEl.attributeValue("class");
                    XmlAnnotations entityAnnotations = new XmlAnnotations(className);
                    for (Element annotEl : Dom4j.elements(entityEl, "annotation")) {
                        XmlAnnotation xmlAnnotation = new XmlAnnotation(
                                inferMetaAnnotationType(annotEl.attributeValue("value")));
                        for (Element attrEl : Dom4j.elements(annotEl, "attribute")) {
                            Object value = getXmlAnnotationAttributeValue(attrEl);
                            xmlAnnotation.attributes.put(attrEl.attributeValue("name"), value);
                        }
                        entityAnnotations.annotations.put(annotEl.attributeValue("name"), xmlAnnotation);
                    }
                    for (Element propEl : Dom4j.elements(entityEl, "property")) {
                        XmlAnnotations attributeAnnotations = new XmlAnnotations(propEl.attributeValue("name"));
                        for (Element annotEl : Dom4j.elements(propEl, "annotation")) {
                            XmlAnnotation xmlAnnotation = new XmlAnnotation(
                                    inferMetaAnnotationType(annotEl.attributeValue("value")));
                            for (Element attrEl : Dom4j.elements(annotEl, "attribute")) {
                                Object value = getXmlAnnotationAttributeValue(attrEl);
                                xmlAnnotation.attributes.put(attrEl.attributeValue("name"), value);
                            }
                            attributeAnnotations.annotations.put(annotEl.attributeValue("name"), xmlAnnotation);
                        }
                        entityAnnotations.attributeAnnotations.add(attributeAnnotations);
                    }
                    result.add(entityAnnotations);
                }
            }
        }
        return result;
    }

    private Object getXmlAnnotationAttributeValue(Element attributeEl) {
        String value = attributeEl.attributeValue("value");
        String className = attributeEl.attributeValue("class");
        String datatypeName = attributeEl.attributeValue("datatype");

        List<Element> values = Dom4j.elements(attributeEl, "value");
        if (StringUtils.isNotBlank(value)) {
            if (!values.isEmpty())
                throw new IllegalStateException(
                        "Both 'value' attribute and 'value' element(s) are specified for attribute "
                                + attributeEl.attributeValue("name"));
            return getXmlAnnotationAttributeValue(value, className, datatypeName);
        }
        if (!values.isEmpty()) {
            Object val0 = getXmlAnnotationAttributeValue(values.get(0).getTextTrim(), className, datatypeName);
            Object array = Array.newInstance(val0.getClass(), values.size());
            Array.set(array, 0, val0);
            for (int i = 1; i < values.size(); i++) {
                Object val = getXmlAnnotationAttributeValue(values.get(i).getTextTrim(), className, datatypeName);
                Array.set(array, i, val);
            }
            return array;
        }
        return null;
    }

    private Object getXmlAnnotationAttributeValue(String value, String className, String datatypeName) {
        if (className == null && datatypeName == null)
            return inferMetaAnnotationType(value);
        if (className != null) {
            Class aClass = ReflectionHelper.getClass(className);
            if (aClass.isEnum()) {
                //noinspection unchecked
                return Enum.valueOf(aClass, value);
            } else {
                throw new UnsupportedOperationException("Class " + className + "  is not Enum");
            }
        } else {
            try {
                return datatypes.get(datatypeName).parse(value);
            } catch (ParseException e) {
                throw new RuntimeException("Unable to parse XML meta-annotation value", e);
            }
        }
    }

    protected Object inferMetaAnnotationType(String str) {
        Object val;
        if (str != null && (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("false")))
            val = Boolean.valueOf(str);
        else if (str != null && JAVA_CLASS_PATTERN.matcher(str).matches()) {
            try {
                val = ReflectionHelper.loadClass(str);
            } catch (ClassNotFoundException e) {
                val = str;
            }
        } else if (!"".equals(str) && StringUtils.isNumeric(str)) {
            val = Integer.valueOf(str);
        } else
            val = str;
        return val;
    }
}