com.qcadoo.view.internal.xml.ViewDefinitionParserImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.qcadoo.view.internal.xml.ViewDefinitionParserImpl.java

Source

/**
 * ***************************************************************************
 * Copyright (c) 2010 Qcadoo Limited
 * Project: Qcadoo Framework
 * Version: 1.3
 *
 * This file is part of Qcadoo.
 *
 * Qcadoo is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation; either version 3 of the License,
 * or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * ***************************************************************************
 */
package com.qcadoo.view.internal.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.google.common.base.Preconditions;
import com.qcadoo.localization.api.TranslationService;
import com.qcadoo.model.api.DataDefinition;
import com.qcadoo.model.api.DataDefinitionService;
import com.qcadoo.security.api.SecurityRole;
import com.qcadoo.security.api.SecurityRolesService;
import com.qcadoo.view.internal.ComponentDefinition;
import com.qcadoo.view.internal.ComponentOption;
import com.qcadoo.view.internal.api.ComponentPattern;
import com.qcadoo.view.internal.api.ContainerPattern;
import com.qcadoo.view.internal.api.ContextualHelpService;
import com.qcadoo.view.internal.api.EnabledAttribute;
import com.qcadoo.view.internal.api.InternalViewDefinition;
import com.qcadoo.view.internal.api.InternalViewDefinitionService;
import com.qcadoo.view.internal.api.ViewDefinition;
import com.qcadoo.view.internal.hooks.AbstractViewHookDefinition;
import com.qcadoo.view.internal.hooks.HookFactory;
import com.qcadoo.view.internal.hooks.HookType;
import com.qcadoo.view.internal.hooks.ViewConstructionHook;
import com.qcadoo.view.internal.hooks.ViewEventListenerHook;
import com.qcadoo.view.internal.hooks.ViewLifecycleHook;
import com.qcadoo.view.internal.internal.ViewComponentsResolverImpl;
import com.qcadoo.view.internal.internal.ViewDefinitionImpl;
import com.qcadoo.view.internal.patterns.AbstractComponentPattern;
import com.qcadoo.view.internal.ribbon.RibbonParserService;
import com.qcadoo.view.internal.ribbon.model.InternalRibbon;
import com.qcadoo.view.internal.ribbon.model.InternalRibbonActionItem;
import com.qcadoo.view.internal.ribbon.model.InternalRibbonGroup;

@Service
public final class ViewDefinitionParserImpl implements ViewDefinitionParser {

    private static final Logger LOG = LoggerFactory.getLogger(ViewDefinitionParserImpl.class);

    @Autowired
    private DataDefinitionService dataDefinitionService;

    @Autowired
    private InternalViewDefinitionService viewDefinitionService;

    @Autowired
    private ViewComponentsResolverImpl viewComponentsResolver;

    @Autowired
    private TranslationService translationService;

    @Autowired
    private ContextualHelpService contextualHelpService;

    @Autowired
    private SecurityRolesService securityRolesService;

    @Autowired
    private HookFactory hookFactory;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private RibbonParserService ribbonService;

    private int currentIndexOrder;

    @Override
    public InternalViewDefinition parseViewXml(final Resource viewXml, final String pluginIdentifier) {
        try {
            return parse(viewXml.getInputStream(), pluginIdentifier);
        } catch (IOException e) {
            throw ViewDefinitionParserException.forFile(viewXml.getFilename(), "Error while reading view resource",
                    e);
        } catch (ViewDefinitionParserNodeException e) {
            throw ViewDefinitionParserException.forFileAndNode(viewXml.getFilename(), e);
        } catch (Exception e) {
            throw ViewDefinitionParserException.forFile(viewXml.getFilename(), e);
        }
    }

    private InternalViewDefinition parse(final InputStream viewDefinitionInputStream, final String pluginIdentifier)
            throws ViewDefinitionParserNodeException {
        try {
            DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = documentBuilder.parse(viewDefinitionInputStream);

            Node root = document.getDocumentElement();

            checkState("view".equals(root.getNodeName()), root, "Wrong root node '" + root.getNodeName() + "'");

            return parseViewDefinition(root, pluginIdentifier);

        } catch (ParserConfigurationException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (SAXException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (IOException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    private InternalViewDefinition parseViewDefinition(final Node viewNode, final String pluginIdentifier)
            throws ViewDefinitionParserNodeException {
        currentIndexOrder = 1;
        String name = getStringAttribute(viewNode, "name");
        Preconditions.checkState(name != null && !"".equals(name.trim()), "Name attribute cannot be empty");

        LOG.info("Reading view " + name + " for plugin " + pluginIdentifier);

        boolean menuAccessible = getBooleanAttribute(viewNode, "menuAccessible", false);

        String windowWidthStr = getStringAttribute(viewNode, "windowWidth");
        String windowHeightStr = getStringAttribute(viewNode, "windowHeight");
        Integer windowWidth = null;
        Integer windowHeight = null;
        if (windowWidthStr != null) {
            windowWidth = Integer.parseInt(windowWidthStr);
        }
        if (windowHeightStr != null) {
            windowHeight = Integer.parseInt(windowHeightStr);
        }

        SecurityRole role = getAuthorizationRole(viewNode);
        DataDefinition dataDefinition = getDataDefinition(viewNode, pluginIdentifier);

        ViewDefinitionImpl viewDefinition = new ViewDefinitionImpl(name, pluginIdentifier, role, dataDefinition,
                menuAccessible, translationService);

        viewDefinition.setWindowDimmension(windowWidth, windowHeight);

        ComponentPattern root = null;

        NodeList childNodes = viewNode.getChildNodes();

        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            if (Node.ELEMENT_NODE != child.getNodeType()) {
                continue;
            }
            if ("component".equals(child.getNodeName())) {
                root = parseComponent(child, viewDefinition, null, pluginIdentifier);
            } else if ("hooks".equals(child.getNodeName())) {
                parseViewHooks(child, viewDefinition);
            } else {
                throw new ViewDefinitionParserNodeException(child, "Unknown node: " + child.getNodeName());
            }
        }

        viewDefinition.addComponentPattern(root);
        viewDefinition.initialize();
        viewDefinition.registerViews(viewDefinitionService);

        return viewDefinition;
    }

    private DataDefinition getDataDefinition(final Node viewNode, final String pluginIdentifier) {
        String modelName = getStringAttribute(viewNode, "modelName");
        if (modelName != null) {
            // FIXME maku upgrade commons-lang to version in which defaultIfNull method is generic.
            // Explicit type casts are so awful :(
            String modelPluginIdentifier = (String) ObjectUtils
                    .defaultIfNull(getStringAttribute(viewNode, "modelPlugin"), pluginIdentifier);
            return dataDefinitionService.get(modelPluginIdentifier, modelName);
        }
        return null;
    }

    public SecurityRole getAuthorizationRole(final Node node) throws ViewDefinitionParserNodeException {
        String authorizationRole = getStringAttribute(node, "defaultAuthorizationRole");
        SecurityRole role;
        if (authorizationRole != null) {
            role = securityRolesService.getRoleByIdentifier(authorizationRole);
            if (role == null) {
                throw new ViewDefinitionParserNodeException(node, "no such role: '" + authorizationRole + "'");
            }
        } else {
            role = securityRolesService.getRoleByIdentifier("ROLE_USER");
        }
        return role;
    }

    @Override
    public Boolean getBooleanAttribute(final Node node, final String name, final boolean defaultValue) {
        Node attribute = getAttribute(node, name);
        if (attribute == null) {
            return defaultValue;
        }
        return Boolean.valueOf(attribute.getNodeValue());
    }

    @Override
    public String getStringAttribute(final Node node, final String name) {
        Node attribute = getAttribute(node, name);
        if (attribute == null) {
            return null;
        }
        return attribute.getNodeValue();
    }

    @Override
    public String getStringNodeContent(final Node node) {
        NodeList childNodes = node.getChildNodes();
        StringBuilder contentSB = new StringBuilder();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            if (child.getNodeType() == Node.CDATA_SECTION_NODE || child.getNodeType() == Node.TEXT_NODE) {
                contentSB.append(child.getNodeValue());
            }
        }
        return contentSB.toString().trim();
    }

    @Override
    public Node getRootOfXmlDocument(final Resource xmlFile) {
        try {
            DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = documentBuilder.parse(xmlFile.getInputStream());
            return document.getDocumentElement();
        } catch (ParserConfigurationException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (SAXException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (IOException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    private Node getAttribute(final Node node, final String name) {
        if (node == null || node.getAttributes() == null) {
            return null;
        }
        return node.getAttributes().getNamedItem(name);
    }

    @Override
    public ComponentOption parseOption(final Node optionNode) {
        Map<String, String> attributes = new HashMap<String, String>();

        NamedNodeMap attributesNodes = optionNode.getAttributes();

        for (int i = 0; i < attributesNodes.getLength(); i++) {
            attributes.put(attributesNodes.item(i).getNodeName(), attributesNodes.item(i).getNodeValue());
        }
        String type = getStringAttribute(optionNode, "type");
        if (type == null) {
            type = getStringAttribute(optionNode, "xsi:type");
        }
        return new ComponentOption(type, attributes);
    }

    public ComponentPattern parseComponent(final Node componentNode, final ViewDefinition viewDefinition,
            final ContainerPattern parent, final String pluginIdentifier) throws ViewDefinitionParserNodeException {
        String type = getStringAttribute(componentNode, "type");

        if (parent == null && !("window".equals(type) || "tabWindow".equals(type))) {
            throw new ViewDefinitionParserNodeException(componentNode, "Unsupported component: " + type);
        }

        try {
            ComponentPattern component = viewComponentsResolver.getComponentInstance(type,
                    getComponentDefinition(componentNode, parent, viewDefinition));
            component.parse(componentNode, this);
            return component;
        } catch (IllegalStateException e) {
            throw new ViewDefinitionParserNodeException(componentNode, e);
        }
    }

    @Override
    public ComponentDefinition getComponentDefinition(final Node componentNode, final ContainerPattern parent,
            final ViewDefinition viewDefinition) {
        String name = getStringAttribute(componentNode, "name");
        String fieldPath = getStringAttribute(componentNode, "field");
        String sourceFieldPath = getStringAttribute(componentNode, "source");
        String plugin = getStringAttribute(componentNode, "plugin");
        String model = getStringAttribute(componentNode, "model");

        DataDefinition customDataDefinition = null;

        if (model != null) {
            String modelPluginIdentifier = plugin == null ? viewDefinition.getPluginIdentifier() : plugin;
            customDataDefinition = dataDefinitionService.get(modelPluginIdentifier, model);
        }

        ComponentDefinition componentDefinition = new ComponentDefinition();
        componentDefinition.setName(name);
        componentDefinition.setFieldPath(fieldPath);
        componentDefinition.setSourceFieldPath(sourceFieldPath);
        componentDefinition.setParent(parent);
        componentDefinition.setTranslationService(translationService);
        componentDefinition.setContextualHelpService(contextualHelpService);
        componentDefinition.setViewDefinition(viewDefinition);
        componentDefinition.setReference(getStringAttribute(componentNode, "reference"));
        componentDefinition.setDefaultVisible(getBooleanAttribute(componentNode, "defaultVisible", true));
        componentDefinition.setHasLabel(getBooleanAttribute(componentNode, "hasLabel", true));
        componentDefinition.setHasDescription(getBooleanAttribute(componentNode, "hasDescription", false));
        componentDefinition.setDataDefinition(customDataDefinition);
        componentDefinition.setApplicationContext(applicationContext);

        EnabledAttribute enabledAttribute = EnabledAttribute.TRUE;
        final String defaultEnabled = getStringAttribute(componentNode, "defaultEnabled");
        if (defaultEnabled != null) {
            enabledAttribute = EnabledAttribute.parseString(defaultEnabled);
        }
        componentDefinition.setDefaultEnabled(EnabledAttribute.TRUE.equals(enabledAttribute));
        componentDefinition.setPermanentlyDisabled(EnabledAttribute.NEVER.equals(enabledAttribute));

        return componentDefinition;
    }

    @Override
    public ComponentPattern parseComponent(final Node componentNode, final ContainerPattern parent)
            throws ViewDefinitionParserNodeException {
        return parseComponent(componentNode, ((AbstractComponentPattern) parent).getViewDefinition(), parent,
                ((AbstractComponentPattern) parent).getViewDefinition().getPluginIdentifier());
    }

    @Override
    public ViewEventListenerHook parseEventListener(final Node listenerNode)
            throws ViewDefinitionParserNodeException {
        try {
            return parseEventListenerHook(listenerNode);
        } catch (Exception e) {
            throw new ViewDefinitionParserNodeException(listenerNode, e);
        }
    }

    private void parseViewHooks(final Node hookNode, final ViewDefinitionImpl viewDefinition)
            throws ViewDefinitionParserNodeException {
        NodeList childNodes = hookNode.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            if (Node.ELEMENT_NODE != child.getNodeType()) {
                continue;
            }
            try {
                viewDefinition.addHook(parseHook(child, viewDefinition, child));
            } catch (Exception e) {
                throw new ViewDefinitionParserNodeException(child, e);
            }
        }
    }

    private AbstractViewHookDefinition parseHook(final Node hookNode, final ViewDefinitionImpl viewDefinition,
            final Node child) {
        HookType hookType = HookType.parseString(hookNode.getNodeName());
        HookType.Category hookCategory = hookType.getCategory();
        if (hookCategory == HookType.Category.CONSTRUCTION_HOOK) {
            return parseConstructionHook(child);
        } else if (hookCategory == HookType.Category.LIFECYCLE_HOOK) {
            return parseLifecycleHook(child);
        }
        throw new IllegalArgumentException("Unsupported hook type: " + hookType);
    }

    public ViewLifecycleHook parseLifecycleHook(final Node hookNode) {
        String fullyQualifiedClassName = getStringAttribute(hookNode, "class");
        String methodName = getStringAttribute(hookNode, "method");
        HookType hookType = HookType.parseString(hookNode.getNodeName());
        return hookFactory.buildViewLifecycleHook(fullyQualifiedClassName, methodName, null, hookType);
    }

    public ViewConstructionHook parseConstructionHook(final Node hookNode) {
        String fullyQualifiedClassName = getStringAttribute(hookNode, "class");
        String methodName = getStringAttribute(hookNode, "method");
        return hookFactory.buildViewConstructionHook(fullyQualifiedClassName, methodName, null);
    }

    public ViewEventListenerHook parseEventListenerHook(final Node hookNode) {
        String fullyQualifiedClassName = getStringAttribute(hookNode, "class");
        String methodName = getStringAttribute(hookNode, "method");
        String eventName = getStringAttribute(hookNode, "event");
        return hookFactory.buildViewEventListener(eventName, fullyQualifiedClassName, methodName, null);
    }

    public int getCurrentIndexOrder() {
        return currentIndexOrder++;
    }

    @Override
    public List<Node> geElementChildren(final Node node) {
        List<Node> result = new LinkedList<Node>();
        NodeList childNodes = node.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            if (child.getNodeType() == Node.ELEMENT_NODE) {
                result.add(child);
            }
        }
        return result;
    }

    @Override
    public ViewExtension getViewExtensionNode(final InputStream resource, final String tagType)
            throws ViewDefinitionParserNodeException {
        try {
            DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = documentBuilder.parse(resource);

            Node root = document.getDocumentElement();

            checkState(root.getNodeName().equals(tagType), root,
                    "Wrong root node name '" + root.getNodeName() + "'");

            String plugin = getStringAttribute(root, "plugin");
            String view = getStringAttribute(root, "view");

            checkState(plugin != null, root, "View extension error: plugin not defined");
            checkState(view != null, root, "View extension error: view not defined");

            return new ViewExtension(plugin, view, root);

        } catch (ParserConfigurationException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (SAXException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (IOException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    @Override
    public InternalRibbon parseRibbon(final Node groupNode, final ViewDefinition viewDefinition)
            throws ViewDefinitionParserNodeException {
        return ribbonService.parseRibbon(groupNode, this, viewDefinition);
    }

    @Override
    public InternalRibbonGroup parseRibbonGroup(final Node groupNode, final ViewDefinition viewDefinition)
            throws ViewDefinitionParserNodeException {
        return ribbonService.parseRibbonGroup(groupNode, this, viewDefinition);
    }

    @Override
    public InternalRibbonActionItem parseRibbonItem(final Node itemNode, final ViewDefinition viewDefinition)
            throws ViewDefinitionParserNodeException {
        return ribbonService.parseRibbonItem(itemNode, this, viewDefinition);
    }

    @Override
    public void checkState(final boolean state, final Node node, final String message)
            throws ViewDefinitionParserNodeException {
        if (!state) {
            throw new ViewDefinitionParserNodeException(node, message);
        }
    }
}