org.kuali.rice.krad.devtools.maintainablexml.MaintainableXMLConversionServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.rice.krad.devtools.maintainablexml.MaintainableXMLConversionServiceImpl.java

Source

/**
 * Copyright 2005-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 *
 * 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.kuali.rice.krad.devtools.maintainablexml;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.AbstractResource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MaintainableXMLConversionServiceImpl {

    private static final String SERIALIZATION_ATTRIBUTE = "serialization";
    private static final String CLASS_ATTRIBUTE = "class";
    private static final String MAINTENANCE_ACTION_ELEMENT_NAME = "maintenanceAction";
    private static final String OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME = "oldMaintainableObject";
    private static final String NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME = "newMaintainableObject";

    private Map<String, String> classNameRuleMap;
    private Map<String, Map<String, String>> classPropertyRuleMap;
    private Map<String, String> dateRuleMap;

    public MaintainableXMLConversionServiceImpl() {
    }

    public String transformMaintainableXML(String xml) {
        String beginning = StringUtils.substringBefore(xml, "<" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");
        String oldMaintainableObjectXML = StringUtils.substringBetween(xml,
                "<" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">",
                "</" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");
        String newMaintainableObjectXML = StringUtils.substringBetween(xml,
                "<" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">",
                "</" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");
        String ending = StringUtils.substringAfter(xml, "</" + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">");

        String convertedOldMaintainableObjectXML = transformSection(oldMaintainableObjectXML);
        String convertedNewMaintainableObjectXML = transformSection(newMaintainableObjectXML);

        String convertedXML = beginning + "<" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">"
                + convertedOldMaintainableObjectXML + "</" + OLD_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">" + "<"
                + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">" + convertedNewMaintainableObjectXML + "</"
                + NEW_MAINTAINABLE_OBJECT_ELEMENT_NAME + ">" + ending;
        return convertedXML;
    }

    private String transformSection(String xml) {

        String maintenanceAction = StringUtils.substringBetween(xml, "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">",
                "</" + MAINTENANCE_ACTION_ELEMENT_NAME + ">");
        xml = StringUtils.substringBefore(xml, "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">");

        try {
            xml = upgradeBONotes(xml);
            if (classNameRuleMap == null) {
                setRuleMaps();
            }

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.parse(new InputSource(new StringReader(xml)));

            removePersonObjects(document);

            for (Node childNode = document.getFirstChild(); childNode != null;) {
                Node nextChild = childNode.getNextSibling();
                transformClassNode(document, childNode);
                childNode = nextChild;
            }

            TransformerFactory transFactory = TransformerFactory.newInstance();
            Transformer trans = transFactory.newTransformer();
            trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            trans.setOutputProperty(OutputKeys.INDENT, "yes");

            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            DOMSource source = new DOMSource(document);
            trans.transform(source, result);
            xml = writer.toString().replaceAll("(?m)^\\s+\\n", "");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return xml + "<" + MAINTENANCE_ACTION_ELEMENT_NAME + ">" + maintenanceAction + "</"
                + MAINTENANCE_ACTION_ELEMENT_NAME + ">";
    }

    /**
     * Upgrades the old Bo notes tag that was part of the maintainable to the new notes tag.
     *
     * @param oldXML - the xml to upgrade
     * @throws Exception
     */
    private String upgradeBONotes(String oldXML) throws Exception {
        // Get the old bo note xml
        String notesXml = StringUtils.substringBetween(oldXML, "<boNotes>", "</boNotes>");
        if (notesXml != null) {
            notesXml = notesXml.replace("org.kuali.rice.kns.bo.Note", "org.kuali.rice.krad.bo.Note");
            notesXml = "<org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl>\n" + notesXml
                    + "\n</org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl>";
            oldXML = oldXML.replaceFirst(">", ">\n<notes>\n" + notesXml + "\n</notes>");
        }
        return oldXML;
    }

    public void removePersonObjects(Document doc) {
        XPath xpath = XPathFactory.newInstance().newXPath();
        XPathExpression personProperties = null;
        try {
            personProperties = xpath.compile("//*[@class='org.kuali.rice.kim.impl.identity.PersonImpl']");
            NodeList matchingNodes = (NodeList) personProperties.evaluate(doc, XPathConstants.NODESET);
            for (int i = 0; i < matchingNodes.getLength(); i++) {
                Node tempNode = matchingNodes.item(i);
                tempNode.getParentNode().removeChild(tempNode);
            }
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }
    }

    private void transformClassNode(Document document, Node node)
            throws ClassNotFoundException, XPathExpressionException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException, InstantiationException {
        String className = node.getNodeName();
        if (this.classNameRuleMap.containsKey(className)) {
            String newClassName = this.classNameRuleMap.get(className);
            document.renameNode(node, null, newClassName);
            className = newClassName;
        }
        Class<?> dataObjectClass = Class.forName(className);
        if (classPropertyRuleMap.containsKey(className)) {
            transformNode(document, node, dataObjectClass, classPropertyRuleMap.get(className));
        }
        transformNode(document, node, dataObjectClass, classPropertyRuleMap.get("*"));
    }

    private void transformNode(Document document, Node node, Class<?> currentClass,
            Map<String, String> propertyMappings) throws ClassNotFoundException, XPathExpressionException,
            IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException {
        for (Node childNode = node.getFirstChild(); childNode != null;) {
            Node nextChild = childNode.getNextSibling();
            String propertyName = childNode.getNodeName();
            if (childNode.hasAttributes()) {
                XPath xpath = XPathFactory.newInstance().newXPath();
                Node serializationAttribute = childNode.getAttributes().getNamedItem(SERIALIZATION_ATTRIBUTE);
                if (serializationAttribute != null
                        && StringUtils.equals(serializationAttribute.getNodeValue(), "custom")) {
                    Node classAttribute = childNode.getAttributes().getNamedItem(CLASS_ATTRIBUTE);
                    if (classAttribute != null && StringUtils.equals(classAttribute.getNodeValue(),
                            "org.kuali.rice.kns.util.TypedArrayList")) {
                        ((Element) childNode).removeAttribute(SERIALIZATION_ATTRIBUTE);
                        ((Element) childNode).removeAttribute(CLASS_ATTRIBUTE);
                        XPathExpression listSizeExpression = xpath.compile("//" + propertyName
                                + "/org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl/default/size/text()");
                        String size = (String) listSizeExpression.evaluate(childNode, XPathConstants.STRING);
                        List<Node> nodesToAdd = new ArrayList<Node>();
                        if (StringUtils.isNotBlank(size) && Integer.valueOf(size) > 0) {
                            XPathExpression listTypeExpression = xpath.compile("//" + propertyName
                                    + "/org.kuali.rice.kns.util.TypedArrayList/default/listObjectType/text()");
                            String listType = (String) listTypeExpression.evaluate(childNode,
                                    XPathConstants.STRING);
                            XPathExpression listContentsExpression = xpath.compile("//" + propertyName
                                    + "/org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl/" + listType);
                            NodeList listContents = (NodeList) listContentsExpression.evaluate(childNode,
                                    XPathConstants.NODESET);
                            for (int i = 0; i < listContents.getLength(); i++) {
                                Node tempNode = listContents.item(i);
                                transformClassNode(document, tempNode);
                                nodesToAdd.add(tempNode);
                            }
                        }
                        for (Node removeNode = childNode.getFirstChild(); removeNode != null;) {
                            Node nextRemoveNode = removeNode.getNextSibling();
                            childNode.removeChild(removeNode);
                            removeNode = nextRemoveNode;
                        }
                        for (Node nodeToAdd : nodesToAdd) {
                            childNode.appendChild(nodeToAdd);
                        }
                    } else {
                        ((Element) childNode).removeAttribute(SERIALIZATION_ATTRIBUTE);

                        XPathExpression mapContentsExpression = xpath.compile("//" + propertyName + "/map/string");
                        NodeList mapContents = (NodeList) mapContentsExpression.evaluate(childNode,
                                XPathConstants.NODESET);
                        List<Node> nodesToAdd = new ArrayList<Node>();
                        if (mapContents.getLength() > 0 && mapContents.getLength() % 2 == 0) {
                            for (int i = 0; i < mapContents.getLength(); i++) {
                                Node keyNode = mapContents.item(i);
                                Node valueNode = mapContents.item(++i);
                                Node entryNode = document.createElement("entry");
                                entryNode.appendChild(keyNode);
                                entryNode.appendChild(valueNode);
                                nodesToAdd.add(entryNode);
                            }
                        }
                        for (Node removeNode = childNode.getFirstChild(); removeNode != null;) {
                            Node nextRemoveNode = removeNode.getNextSibling();
                            childNode.removeChild(removeNode);
                            removeNode = nextRemoveNode;
                        }
                        for (Node nodeToAdd : nodesToAdd) {
                            childNode.appendChild(nodeToAdd);
                        }
                    }
                }
            }
            if (propertyMappings != null && propertyMappings.containsKey(propertyName)) {
                String newPropertyName = propertyMappings.get(propertyName);
                if (StringUtils.isNotBlank(newPropertyName)) {
                    document.renameNode(childNode, null, newPropertyName);
                    propertyName = newPropertyName;
                } else {
                    // If there is no replacement name then the element needs
                    // to be removed and skip all other processing
                    node.removeChild(childNode);
                    childNode = nextChild;
                    continue;
                }
            }

            if (dateRuleMap != null && dateRuleMap.containsKey(propertyName)) {
                String newDateValue = dateRuleMap.get(propertyName);
                if (StringUtils.isNotBlank(newDateValue)) {
                    if (childNode.getTextContent().length() == 10) {
                        childNode.setTextContent(childNode.getTextContent() + " " + newDateValue);

                    }
                }
            }

            if (currentClass != null) {
                if (childNode.hasChildNodes() && !(Collection.class.isAssignableFrom(currentClass)
                        || Map.class.isAssignableFrom(currentClass))) {
                    Class<?> propertyClass = PropertyUtils.getPropertyType(currentClass.newInstance(),
                            propertyName);
                    if (propertyClass != null && classPropertyRuleMap.containsKey(propertyClass.getName())) {
                        transformNode(document, childNode, propertyClass,
                                this.classPropertyRuleMap.get(propertyClass.getName()));
                    }
                    transformNode(document, childNode, propertyClass, classPropertyRuleMap.get("*"));
                }
            }
            childNode = nextChild;
        }
    }

    /**
     * Reads the rule xml and sets up the rule maps that will be used to transform the xml
     */
    private void setRuleMaps() {
        setupConfigurationMaps();
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();

            AbstractResource resource = null;

            Document doc = db.parse(getClass().getResourceAsStream(
                    "/org/kuali/rice/devtools/krad/maintainablexml/MaintainableXMLUpgradeRules.xml"));

            doc.getDocumentElement().normalize();
            XPath xpath = XPathFactory.newInstance().newXPath();

            // Get the moved classes rules
            XPathExpression exprClassNames = xpath.compile("//*[@name='maint_doc_classname_changes']/pattern");
            NodeList classNamesList = (NodeList) exprClassNames.evaluate(doc, XPathConstants.NODESET);
            for (int s = 0; s < classNamesList.getLength(); s++) {
                String matchText = xpath.evaluate("match/text()", classNamesList.item(s));
                String replaceText = xpath.evaluate("replacement/text()", classNamesList.item(s));
                classNameRuleMap.put(matchText, replaceText);
            }

            // Get the property changed rules

            XPathExpression exprClassProperties = xpath
                    .compile("//*[@name='maint_doc_changed_class_properties']/pattern");
            XPathExpression exprClassPropertiesPatterns = xpath.compile("pattern");
            NodeList propertyClassList = (NodeList) exprClassProperties.evaluate(doc, XPathConstants.NODESET);
            for (int s = 0; s < propertyClassList.getLength(); s++) {
                String classText = xpath.evaluate("class/text()", propertyClassList.item(s));
                Map<String, String> propertyRuleMap = new HashMap<String, String>();
                NodeList classPropertiesPatterns = (NodeList) exprClassPropertiesPatterns
                        .evaluate(propertyClassList.item(s), XPathConstants.NODESET);
                for (int c = 0; c < classPropertiesPatterns.getLength(); c++) {
                    String matchText = xpath.evaluate("match/text()", classPropertiesPatterns.item(c));
                    String replaceText = xpath.evaluate("replacement/text()", classPropertiesPatterns.item(c));
                    propertyRuleMap.put(matchText, replaceText);
                }
                classPropertyRuleMap.put(classText, propertyRuleMap);
            }

            // Get the Date rules
            XPathExpression dateFieldNames = xpath.compile("//*[@name='maint_doc_date_changes']/pattern");
            NodeList DateNamesList = (NodeList) dateFieldNames.evaluate(doc, XPathConstants.NODESET);
            for (int s = 0; s < DateNamesList.getLength(); s++) {
                String matchText = xpath.evaluate("match/text()", DateNamesList.item(s));
                String replaceText = xpath.evaluate("replacement/text()", DateNamesList.item(s));
                dateRuleMap.put(matchText, replaceText);
            }
        } catch (Exception e) {
            System.out.println("Error parsing rule xml file. Please check file. : " + e.getMessage());
            e.printStackTrace();
        }
    }

    private void setupConfigurationMaps() {
        classNameRuleMap = new HashMap<String, String>();
        classPropertyRuleMap = new HashMap<String, Map<String, String>>();
        dateRuleMap = new HashMap<String, String>();

        // Pre-populate the class property rules with some defaults which apply to every BO
        Map<String, String> defaultPropertyRules = new HashMap<String, String>();
        defaultPropertyRules.put("boNotes", "");
        defaultPropertyRules.put("autoIncrementSet", "");
        classPropertyRuleMap.put("*", defaultPropertyRules);
    }
}