com.bstek.dorado.view.config.XmlDocumentPreprocessor.java Source code

Java tutorial

Introduction

Here is the source code for com.bstek.dorado.view.config.XmlDocumentPreprocessor.java

Source

/*
 * This file is part of Dorado 7.x (http://dorado7.bsdn.org).
 * 
 * Copyright (c) 2002-2012 BSTEK Corp. All rights reserved.
 * 
 * This file is dual-licensed under the AGPLv3 (http://www.gnu.org/licenses/agpl-3.0.html) 
 * and BSDN commercial (http://www.bsdn.org/licenses) licenses.
 * 
 * If you are unsure which license is appropriate for your use, please contact the sales department
 * at http://www.bstek.com/contact.
 */

package com.bstek.dorado.view.config;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import com.bstek.dorado.config.xml.XmlConstants;
import com.bstek.dorado.config.xml.XmlParseException;
import com.bstek.dorado.core.el.Expression;
import com.bstek.dorado.core.el.ExpressionHandler;
import com.bstek.dorado.core.io.Resource;
import com.bstek.dorado.util.Assert;
import com.bstek.dorado.util.xml.DomUtils;
import com.bstek.dorado.view.config.xml.PreparseContext;
import com.bstek.dorado.view.config.xml.ViewConfigParserUtils;
import com.bstek.dorado.view.config.xml.ViewXmlConstants;
import com.bstek.dorado.view.manager.ViewConfigManager;
import com.bstek.dorado.view.registry.ComponentTypeRegistry;

/**
 * ?ImportXML?
 * 
 * @author Benny Bao (mailto:benny.bao@bstek.com)
 * @since 2010-12-26
 */
public class XmlDocumentPreprocessor {
    private static Set<String> specialMergeProperties = new HashSet<String>();

    {
        specialMergeProperties.add(ViewXmlConstants.ATTRIBUTE_PACKAGES);
        specialMergeProperties.add(ViewXmlConstants.ATTRIBUTE_JAVASCRIPT_FILE);
        specialMergeProperties.add(ViewXmlConstants.ATTRIBUTE_STYLESHEET_FILE);
        specialMergeProperties.add(ViewXmlConstants.ATTRIBUTE_METADATA);
    }

    private ViewConfigManager viewConfigManager;
    private ComponentTypeRegistry componentTypeRegistry;
    private ExpressionHandler expressionHandler;

    public void setViewConfigManager(ViewConfigManager viewConfigManager) {
        this.viewConfigManager = viewConfigManager;
    }

    public void setComponentTypeRegistry(ComponentTypeRegistry componentTypeRegistry) {
        this.componentTypeRegistry = componentTypeRegistry;
    }

    public void setExpressionHandler(ExpressionHandler expressionHandler) {
        this.expressionHandler = expressionHandler;
    }

    private Element doFindElementById(Element element, String id) {
        Element result = null;
        Node child = element.getFirstChild();
        while (child != null) {
            if (child instanceof Element) {
                Element childElement = (Element) child;
                if (id.equals(childElement.getAttribute(ViewXmlConstants.ATTRIBUTE_ID))) {
                    result = childElement;
                    break;
                }
            }
            child = child.getNextSibling();
        }

        if (result == null) {
            child = element.getFirstChild();
            while (child != null) {
                if (child instanceof Element) {
                    result = doFindElementById((Element) child, id);
                    if (result != null)
                        break;
                }
                child = child.getNextSibling();
            }
        }
        return result;
    }

    private Element findElementById(Document document, String id, PreparseContext context) throws Exception {
        Element element = null;
        Element documentElement = document.getDocumentElement();
        Element modelElement = ViewConfigParserUtils.findModelElement(documentElement, context.getResource());
        if (modelElement != null) {
            element = doFindElementById(modelElement, id);
        }
        if (element == null) {
            Element viewElement = ViewConfigParserUtils.findViewElement(documentElement, context.getResource());
            element = doFindElementById(viewElement, id);
        }
        return element;
    }

    @SuppressWarnings("unchecked")
    private Element getGroupStartElement(Document document, String contentId, PreparseContext context)
            throws Exception {
        Element element = null;
        Map<String, Element> cache = (Map<String, Element>) document.getUserData("contentStartElementCache");
        if (cache != null && cache.containsKey(contentId)) {
            element = cache.get(contentId);
            return element;
        }

        element = findElementById(document, contentId, context);
        if (cache == null) {
            cache = new HashMap<String, Element>();
            document.setUserData("contentStartElementCache", cache, null);
        }
        cache.put(contentId, element);
        return element;
    }

    private ViewConfigInfo getViewConfigModelInfo(String viewName) throws Exception {
        Object viewConfigFactory = viewConfigManager.getViewConfigFactory(viewName);
        if (viewConfigFactory instanceof ViewConfigDefinitionFactory) {
            ViewConfigInfo viewConfigInfo = ((ViewConfigDefinitionFactory) viewConfigFactory)
                    .getViewConfigInfo(viewName);
            return viewConfigInfo;
        } else {
            throw new XmlParseException("Can not find a according ViewDefinitionFactory for [" + viewName + "].");
        }
    }

    @SuppressWarnings("unchecked")
    private void postProcessNodeReplacement(Element element) {
        Document ownerDocument = element.getOwnerDocument();
        Node child = element.getFirstChild();
        while (child != null) {
            Node nextChild = child.getNextSibling();
            if (child instanceof Element) {
                if (child.getUserData("dorado.delete") != null) {
                    List<Element> replaceContent = (List<Element>) child.getUserData("dorado.replace");
                    if (replaceContent != null) {
                        for (Element el : replaceContent) {
                            Element clonedElement = (Element) ownerDocument.importNode(el, true);
                            element.insertBefore(clonedElement, child);
                        }
                        child.setUserData("dorado.replace", null, null);
                    }
                    element.removeChild(child);
                    child.setUserData("dorado.delete", null, null);
                }
            }
            child = nextChild;
        }
    }

    private List<Element> getImportContent(Element importElement, PreparseContext context) throws Exception {
        String src = importElement.getAttribute("src");

        if (StringUtils.isNotEmpty(src)) {
            Expression expression = expressionHandler.compile(src);
            if (expression != null) {
                src = (String) expression.evaluate();
            }
        }

        if (StringUtils.isEmpty(src)) {
            throw new IllegalArgumentException("Import src undefined");
        }

        int i = src.lastIndexOf('#');
        if (i < 0) {
            throw new IllegalArgumentException("[groupId/componentId] missed");
        }

        String viewName = src.substring(0, i);
        String groupId = src.substring(i + 1);
        Assert.notEmpty(viewName, "Import viewName undefined.");
        Assert.notEmpty(groupId, "Import groupId/componentId undefined.");

        ViewConfigInfo viewConfigInfo = getViewConfigModelInfo(viewName);
        if (viewConfigInfo == null) {
            throw new XmlParseException("Import view not found [" + src + "].", context);
        }

        Document document = (Document) viewConfigInfo.getConfigModel();
        List<Element> importContent = null;
        Element groupStartElement = getGroupStartElement(document, groupId, context);
        if (groupStartElement != null) {
            importContent = new ArrayList<Element>();
            String nodeName = groupStartElement.getNodeName();
            if (nodeName.equals(XmlConstants.GROUP_START)) {
                boolean groupEndFound = false;
                Node node = groupStartElement.getNextSibling();
                while (true) {
                    node = node.getNextSibling();
                    if (node == null)
                        break;
                    if (node instanceof Element) {
                        Element element = (Element) node;
                        nodeName = element.getNodeName();
                        if (nodeName.equals(XmlConstants.GROUP_END)) {
                            groupEndFound = true;
                            break;
                        } else if (nodeName.equals(XmlConstants.GROUP_START)) {
                            throw new IllegalArgumentException("Nesting <GroupStart> not supported.");
                        } else {
                            importContent.add(element);
                        }
                    }
                }
                if (!groupEndFound) {
                    throw new IllegalArgumentException("<GroupEnd> not found for [" + groupId + "].");
                }
            } else if (nodeName.equals(XmlConstants.GROUP_END) || nodeName.equals(XmlConstants.PLACE_HOLDER)
                    || nodeName.equals(XmlConstants.PLACE_HOLDER_START)
                    || nodeName.equals(XmlConstants.PLACE_HOLDER_END)) {
                // do nothing
            } else {
                importContent.add(groupStartElement);
            }
        }

        Resource dependentResource = viewConfigInfo.getResource();
        if (dependentResource != null) {
            context.getDependentResources().add(dependentResource);
        }
        return importContent;
    }

    private boolean processImports(List<Element> elements, PreparseContext preparseContext) throws Exception {
        boolean changed = false;
        for (Element childElement : elements.toArray(new Element[0])) {
            String nodeName = childElement.getNodeName();
            if (XmlConstants.IMPORT.equals(nodeName)) {
                List<Element> importContent = getImportContent(childElement, preparseContext);
                if (importContent != null) {
                    childElement.setUserData("dorado.replace", importContent, null);
                }
                childElement.setUserData("dorado.delete", importContent, null);
                changed = true;
            }
        }
        return changed;
    }

    private void processImports(Element element, PreparseContext context) throws Exception {
        if (processImports(DomUtils.getChildElements(element), context)) {
            postProcessNodeReplacement(element);
        }
        for (Element childElement : DomUtils.getChildElements(element)) {
            processImports(childElement, context);
        }
    }

    private List<Element> getPlaceHolderContent(Element placeHolderElement, TemplateContext tempalteContext)
            throws Exception {
        String contentId = placeHolderElement.getAttribute(XmlConstants.ATTRIBUTE_ID);
        List<Element> concreteContent = null;
        Document document = tempalteContext.getSourceDocument();

        Element groupStartElement = getGroupStartElement(document, contentId, tempalteContext);
        if (groupStartElement != null) {
            concreteContent = new ArrayList<Element>();
            String nodeName = groupStartElement.getNodeName();
            if (nodeName.equals(XmlConstants.GROUP_START)) {
                boolean groupEndFound = false;
                Node node = groupStartElement.getNextSibling();
                while (true) {
                    node = node.getNextSibling();
                    if (node == null) {
                        break;
                    }
                    if (node instanceof Element) {
                        Element element = (Element) node;
                        nodeName = element.getNodeName();
                        if (nodeName.equals(XmlConstants.GROUP_END)) {
                            groupEndFound = true;
                            break;
                        } else if (nodeName.equals(XmlConstants.GROUP_START)) {
                            throw new IllegalArgumentException("Nesting <GroupStart> not supported.");
                        } else {
                            concreteContent.add(element);
                        }
                    }
                }
                if (!groupEndFound) {
                    throw new IllegalArgumentException("<GroupEnd> not found for [" + contentId + "].");
                }
            } else if (nodeName.equals(XmlConstants.GROUP_END)) {
                // do nothing
            } else {
                concreteContent.add(groupStartElement);
            }
        }
        return concreteContent;
    }

    private boolean processPlaceHolders(List<Element> elements, TemplateContext templateContext) throws Exception {
        boolean changed = false;
        List<Element> defaultContent = null;
        Element placeHolderStartElement = null;
        for (Element childElement : elements.toArray(new Element[0])) {
            String nodeName = childElement.getNodeName();
            if (XmlConstants.PLACE_HOLDER.equals(nodeName)) {
                List<Element> concreteContent = getPlaceHolderContent(childElement, templateContext);
                if (concreteContent != null) {
                    childElement.setUserData("dorado.replace", concreteContent, null);
                }
                childElement.setUserData("dorado.delete", concreteContent, null);
                changed = true;
            } else if (XmlConstants.PLACE_HOLDER_START.equals(nodeName)) {
                if (templateContext.isInPlaceHolderSection()) {
                    throw new XmlParseException("Nesting <PlaceHolderStart> not supported.", childElement,
                            templateContext);
                } else {
                    templateContext.setInPlaceHolderSection(true);
                    defaultContent = new ArrayList<Element>();
                    placeHolderStartElement = childElement;
                }
                changed = true;
            } else if (XmlConstants.PLACE_HOLDER_END.equals(nodeName)) {
                if (placeHolderStartElement == null) {
                    throw new XmlParseException("<PlaceHolderStart> missed.", childElement, templateContext);
                } else {
                    List<Element> placeHolderContent = getPlaceHolderContent(childElement, templateContext);
                    if (placeHolderContent != null) {
                        placeHolderStartElement.setUserData("dorado.replace", placeHolderContent, null);
                        for (Element el : defaultContent) {
                            el.setUserData("dorado.delete", Boolean.TRUE, null);
                        }
                    }
                    placeHolderStartElement.setUserData("dorado.delete", Boolean.TRUE, null);
                    childElement.setUserData("dorado.delete", Boolean.TRUE, null);

                    templateContext.setInPlaceHolderSection(false);
                    placeHolderStartElement = null;
                    defaultContent = null;
                }
                changed = true;
            } else {
                processPlaceHolders(childElement, templateContext);
            }
        }
        return changed;
    }

    private void processPlaceHolders(Element element, TemplateContext templateContext) throws Exception {
        if (processPlaceHolders(DomUtils.getChildElements(element), templateContext)) {
            postProcessNodeReplacement(element);
        }
    }

    public String getPropertyValue(Element element, String property) {
        String packages = null;
        Node node = element.getAttributeNode(property);
        if (node == null) {
            List<Element> propertyElements = DomUtils.getChildrenByTagName(element, XmlConstants.PROPERTY);
            if (propertyElements != null) {
                for (Element childElement : propertyElements) {
                    if (property.equals(childElement.getAttribute(XmlConstants.ATTRIBUTE_NAME))) {
                        packages = org.springframework.util.xml.DomUtils.getTextValue(childElement);
                        break;
                    }
                }
            }
        } else {
            packages = node.getNodeValue();
        }
        return packages;
    }

    private void mergeTemplateProperty(Element templteViewElement, Element viewElement, String property) {
        String templateValue = getPropertyValue(templteViewElement, property);
        String value = getPropertyValue(viewElement, property);
        if (StringUtils.isNotEmpty(value)) {
            if (StringUtils.isNotEmpty(templateValue)) {
                value = templateValue + ',' + value;
            }
        } else {
            value = templateValue;
        }
        if (StringUtils.isNotEmpty(value)) {
            templteViewElement.setAttribute(property, value);
        }
    }

    private Element findPropertyElement(Element element, String propertyName) {
        for (Element propertyElement : DomUtils.getChildrenByTagName(element, XmlConstants.PROPERTY)) {
            if (propertyName.equals(propertyElement.getAttribute(XmlConstants.ATTRIBUTE_NAME))) {
                return propertyElement;
            }
        }
        return null;
    }

    private void gothroughPlaceHolders(Document templateDocument, TemplateContext templateContext)
            throws Exception {
        Element templteViewElement = ViewConfigParserUtils.findViewElement(templateDocument.getDocumentElement(),
                templateContext.getResource());

        Document document = templateContext.getSourceDocument();
        Element viewElement = ViewConfigParserUtils.findViewElement(document.getDocumentElement(),
                templateContext.getSourceContext().getResource());

        NamedNodeMap attributes = viewElement.getAttributes();
        int len = attributes.getLength();
        for (int i = 0; i < len; i++) {
            Node item = attributes.item(i);
            String nodeName = item.getNodeName();
            if (!specialMergeProperties.contains(nodeName)) {
                templteViewElement.setAttribute(nodeName, item.getNodeValue());
            }
        }

        List<Element> viewProperties = DomUtils.getChildrenByTagName(viewElement, XmlConstants.PROPERTY);
        for (Element propertyElement : viewProperties) {
            String propertyName = propertyElement.getAttribute(XmlConstants.ATTRIBUTE_NAME);
            if (!specialMergeProperties.contains(propertyName)) {
                Element clonedElement = (Element) templateDocument.importNode(propertyElement, true);
                templteViewElement.appendChild(clonedElement);
            }
        }

        mergeMetaData(templateDocument, templteViewElement, viewElement);
        mergeTemplateProperty(templteViewElement, viewElement, ViewXmlConstants.ATTRIBUTE_PACKAGES);
        mergeTemplateProperty(templteViewElement, viewElement, ViewXmlConstants.ATTRIBUTE_JAVASCRIPT_FILE);
        mergeTemplateProperty(templteViewElement, viewElement, ViewXmlConstants.ATTRIBUTE_STYLESHEET_FILE);

        for (Element element : DomUtils.getChildElements(viewElement)) {
            String nodeName = element.getNodeName();
            if (nodeName.equals(XmlConstants.PROPERTY) || nodeName.equals(XmlConstants.GROUP_START)
                    || nodeName.equals(XmlConstants.GROUP_END) || nodeName.equals(XmlConstants.IMPORT)
                    || nodeName.equals(XmlConstants.GROUP_END) || nodeName.equals(XmlConstants.PLACE_HOLDER_START)
                    || nodeName.equals(XmlConstants.PLACE_HOLDER_END)) {
                continue;
            }
            if (componentTypeRegistry.getRegisterInfo(nodeName) == null) {
                Element clonedElement = (Element) templateDocument.importNode(element, true);
                templteViewElement.appendChild(clonedElement);
            }
        }

        processPlaceHolders(templteViewElement, templateContext);
    }

    protected void mergeMetaData(Document templateDocument, Element templteElement, Element element) {
        if (element.getAttributeNode(ViewXmlConstants.ATTRIBUTE_METADATA) != null) {
            templteElement.setAttribute(ViewXmlConstants.ATTRIBUTE_METADATA,
                    element.getAttribute(ViewXmlConstants.ATTRIBUTE_METADATA));
        }

        Element templateMetaDataElement = findPropertyElement(templteElement, ViewXmlConstants.ATTRIBUTE_METADATA);
        Element metaDataElement = findPropertyElement(element, ViewXmlConstants.ATTRIBUTE_METADATA);
        if (metaDataElement != null) {
            if (templateMetaDataElement == null) {
                templateMetaDataElement = templateDocument.createElement(XmlConstants.PROPERTY);
                templateMetaDataElement.setAttribute(XmlConstants.ATTRIBUTE_NAME,
                        ViewXmlConstants.ATTRIBUTE_METADATA);
                templteElement.appendChild(templateMetaDataElement);
            }

            for (Element metaPropertyElement : DomUtils.getChildrenByTagName(metaDataElement,
                    XmlConstants.PROPERTY)) {
                Element clonedElement = (Element) templateDocument.importNode(metaPropertyElement, true);
                templateMetaDataElement.appendChild(clonedElement);
            }
        }
    }

    private Map<String, Element> getArgumentElements(Document document, PreparseContext context) throws Exception {
        Map<String, Element> map = new HashMap<String, Element>();
        Element argumentsElement = ViewConfigParserUtils.findArgumentsElement(document.getDocumentElement(),
                context.getResource());
        if (argumentsElement != null) {
            for (Element argumentElement : DomUtils.getChildrenByTagName(argumentsElement,
                    ViewXmlConstants.ARGUMENT)) {
                map.put(argumentElement.getAttribute(XmlConstants.ATTRIBUTE_NAME), argumentElement);
            }
        }
        return map;
    }

    private void mergeArguments(Document templateDocument, TemplateContext templateContext) throws Exception {
        Document document = templateContext.getSourceDocument();
        Map<String, Element> argumentMap = getArgumentElements(document, templateContext.getSourceContext());
        Map<String, Element> templateArgumentMap = getArgumentElements(templateDocument, templateContext);

        if (!argumentMap.isEmpty()) {
            Element templateDocumentElement = templateDocument.getDocumentElement();
            Element argumentsElement = ViewConfigParserUtils.findArgumentsElement(templateDocumentElement,
                    templateContext.getResource());
            if (argumentsElement == null) {
                argumentsElement = templateDocument.createElement(ViewXmlConstants.ARGUMENTS);
                templateDocumentElement.insertBefore(argumentsElement, templateDocumentElement.getFirstChild());
            }

            for (Map.Entry<String, Element> entry : argumentMap.entrySet()) {
                String name = entry.getKey();
                if (templateArgumentMap == null || !templateArgumentMap.containsKey(name)) {
                    Element element = entry.getValue();
                    Element clonedElement = (Element) templateDocument.importNode(element, true);
                    argumentsElement.appendChild(clonedElement);
                }
            }
        }
    }

    private void mergeModels(Document templateDocument, TemplateContext templateContext) throws Exception {
        Element templateDocumentElement = templateDocument.getDocumentElement();
        Element templateModelElement = ViewConfigParserUtils.findModelElement(templateDocumentElement,
                templateContext.getResource());

        Document document = templateContext.getSourceDocument();
        Element modelElement = ViewConfigParserUtils.findModelElement(document.getDocumentElement(),
                templateContext.getSourceContext().getResource());

        if (modelElement != null) {
            List<Element> modelElements = DomUtils.getChildElements(modelElement);
            if (!modelElements.isEmpty()) {
                if (templateModelElement == null) {
                    templateModelElement = templateDocument.createElement(ViewXmlConstants.MODEL);
                    Element viewElement = ViewConfigParserUtils.findViewElement(templateDocumentElement,
                            templateContext.getResource());
                    templateDocumentElement.insertBefore(templateModelElement, viewElement);
                }

                for (Element model : modelElements) {
                    Element clonedElement = (Element) templateDocument.importNode(model, true);
                    templateModelElement.appendChild(clonedElement);
                }
            }
        }
    }

    private Document loadTemplate(String templateViewName, TemplateContext templateContext) throws Exception {
        ViewConfigInfo viewConfigInfo = getViewConfigModelInfo(templateViewName);
        if (viewConfigInfo == null) {
            throw new XmlParseException("Template view not found [" + templateViewName + "].",
                    templateContext.getSourceContext());
        }

        Document templateDocument = (Document) viewConfigInfo.getConfigModel();
        synchronized (templateDocument) {
            templateDocument = (Document) templateDocument.cloneNode(true);

            mergeMetaData(templateDocument, templateDocument.getDocumentElement(),
                    templateContext.getSourceDocument().getDocumentElement());

            mergeArguments(templateDocument, templateContext);
            mergeModels(templateDocument, templateContext);
            gothroughPlaceHolders(templateDocument, templateContext);
        }
        return templateDocument;
    }

    public Document process(String viewName, Document document) throws Exception {
        synchronized (document) {
            PreparseContext preparseContext = new PreparseContext(viewName);

            Element documentElement = document.getDocumentElement();
            String templateSrc = documentElement.getAttribute(ViewXmlConstants.ATTRIBUTE_TEMPALTE);
            if (StringUtils.isNotEmpty(templateSrc)) {
                TemplateContext templateContext = new TemplateContext(templateSrc, preparseContext, document);
                Document templateDocument = loadTemplate(templateSrc, templateContext);
                document = templateDocument;
                documentElement = document.getDocumentElement();
            }

            Element viewElement = ViewConfigParserUtils.findViewElement(documentElement,
                    preparseContext.getResource());
            processImports(viewElement, preparseContext);
        }
        return document;
    }

}

class TemplateContext extends PreparseContext {
    private PreparseContext sourceContext;
    private Document sourceDocument;

    public PreparseContext getSourceContext() {
        return sourceContext;
    }

    public TemplateContext(String viewName, PreparseContext sourceContext, Document sourceDocument) {
        super(viewName);
        this.sourceContext = sourceContext;
        this.sourceDocument = sourceDocument;
    }

    public Document getSourceDocument() {
        return sourceDocument;
    }
}