org.dvlyyon.net.netconf.marshalling.NetconfUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.dvlyyon.net.netconf.marshalling.NetconfUtil.java

Source

/*
 * This work is free software; you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * This work 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
 * General Public License for more details.
 *
 * You should have received a link to the GNU General Public License along with this program; if not, write
 * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
 * 
 * Copyright Model Based Management Technologies, LLC. (c) 2009 - 2011. All rights reserved.
 *
 * This source code is provided "as is" and without warranties as to performance or merchantability.
 * The author and/or distributors of this source code may have made statements about this source code.
 * Any such statements do not constitute warranties and shall not be relied on by the user in deciding
 * whether to use this source code.
 *
 * This source code is provided without any express or implied warranties whatsoever. Because of the
 * diversity of conditions and hardware under which this source code may be used, no warranty of fitness
 * for a particular purpose is offered. The user is advised to test the source code thoroughly before
 * relying on it. The user must assume the entire risk of using the source code.
 */
package org.dvlyyon.net.netconf.marshalling;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dvlyyon.common.beans.BeanUtil;
import org.dvlyyon.net.netconf.common.Path;
import org.dvlyyon.net.netconf.common.PathSegment;
import org.jdom2.Element;
import org.jdom2.Namespace;

/**
 * The NetconfUtil class provides methods that perform the transformation between POJOs (Plain Oild Java Objects) and corresponding NETCONF XML.
 * It uses the metadata that is provided in the mapping XML to do the actual marsahlling and unmarsahlling.
 *
 * @author  Subramaniam Aiylam
 * @since   1.6
 */
public final class NetconfUtil {

    /** Logger for tracing */
    static protected final Log s_logger = LogFactory.getLog(NetconfUtil.class.getName());

    /**
     * Defines the NETCONF operation to be applied during an <edit-config>.
     */
    public static enum EditOperation {
        /** Not relevant */
        NotApplicable,
        /** Specified for object creation */
        Create,
        /** Specified for object replacement */
        Replace,
        /** Specified for object merge (modification of an existing object) */
        Merge,
        /** Specified for object deletion */
        Delete
    };

    /** The data model, which represents the metadata driving the transformation */
    private DataModel m_model;

    /** Helper class used to marshal choices and unions */
    private UnionChoiceHelper m_unionChoiceHelper;

    /**
     * Creates a NetconfUtil object.
     *
     * @param dataModel  Model containing transformation metadata.
     */
    public NetconfUtil(DataModel dataModel) {
        m_model = dataModel;
        m_unionChoiceHelper = new UnionChoiceHelper(this, m_model);
    }

    /**
     * Given a POJO, converts it to the corresponding (valid) NETCONF XML, which can then be sent over the wire.
     *
     * @param parentXpathOid      XPATH representing the path to the parent object of the POJO.
     * @param oldObject           the old POJO, in case the edit operation specified is an update. We need to pass this in, since
     *                            there may be a specific need to "unset" attributes that were previously set.
     * @param object              the POJO that needs to be converted to NETOCNF XML.
     * @param editOperation       The type of operation to be performed.
     * @return                    XML node representing the data to be sent via NETCONF.
     * @throws RuntimeException   if an error occurred.
     */
    public Element toXml(final Path parentXpathOid, final Object oldObject, final Object object,
            final EditOperation editOperation) throws RuntimeException {
        // Create an xpath based upon the parent's OID, my xmltag and my keys
        Element ret = null;
        ClassMapping classUnderConsideration = m_model.getClassMappingByName(object.getClass().getName());
        if (classUnderConsideration == null) {
            throw new RuntimeException("Class metadata not found for: " + object.getClass().getName());
        }
        Element current = null;
        ClassMapping cm = classUnderConsideration.getParentClass();
        if (cm == null) {
            s_logger.debug("Parent class metadata not found for: " + object.getClass().getName());
        } else {
            //Namespace ns = Namespace.getNamespace(cm.getXmlNamespace());
            final ArrayList<PathSegment> segments = parentXpathOid.getSegments();
            final ArrayList<ClassMapping> mappings = Filter.getClassHierarchy(cm);
            int index = 0;
            for (PathSegment segment : segments) {
                ClassMapping theClass = mappings.get(index);
                Element elem = Filter.processToken(segment.getTag(), theClass);
                if (ret == null) {
                    ret = elem;
                    current = elem;
                } else {
                    current.addContent(elem);
                    current = elem;
                }
                index++;
            }
        }
        // Now we have got all the XML up to our parent in the hierarchy, now for the class itself
        Element classElem = new Element(classUnderConsideration.getXmlTag(),
                Namespace.getNamespace(classUnderConsideration.getXmlNamespace()));
        // Here is where we set the operation to being create, update or delete
        if (editOperation == EditOperation.Delete) {
            classElem.setAttribute("operation", "delete");
        } else if (editOperation == EditOperation.Create) {
            classElem.setAttribute("operation", "create");
        } else if (editOperation == EditOperation.Replace) {
            classElem.setAttribute("operation", "replace");
        } else if (editOperation == EditOperation.Merge) {
            classElem.setAttribute("operation", "merge");
        }
        if (ret == null) {
            ret = classElem;
        } else {
            current.addContent(classElem);
        }
        dumpAttributesToXml(classElem, classUnderConsideration, oldObject, object, editOperation);
        return ret;
    }

    /**
     * Transform all the attributes of the specified POJO to NETCONF XML and add these to the XML node representing the object itself.
     *
     * @param classElem           XML node representing the object.
     * @param cm                  Metadata representing the class being transformed.
     * @param oldObject           the old POJO, in case the edit operation specified is an update. We need to pass this in, since
     *                            there may be a specific need to "unset" attributes that were previously set.
     * @param object              the POJO that needs to be converted to NETOCNF XML.
     * @param editOperation       The type of operation to be performed.
     * @throws RuntimeException   if an error occurred.
     */
    private void dumpAttributesToXml(final Element classElem, final ClassMapping cm, final Object oldObject,
            final Object object, final EditOperation editOperation) throws RuntimeException {
        // Need to dump all the keys first (BEFORE anything else); this should be done during creation
        for (AttributeMapping am : cm.getAttributeMappings()) {
            dumpAttributeToXml(am, classElem, cm, oldObject, object, editOperation);
        }
    }

    /**
     * Given an attribute mapping for a class, convert it to NETCONF XML and add them to the root XML node.
     *
     * @param am                  Attribute mappings defining an attribute of the class.
     * @param classElem           Root XML node representing the object.
     * @param cm                  ClassMapping representing the class being transformed.
     * @param oldObject           the old POJO, in case the edit operation specified is an update. We need to pass this in, since
     *                            there may be a specific need to "unset" attributes that were previously set.
     * @param object              the POJO that needs to be converted to NETOCNF XML.
     * @param editOperation       The type of operation to be performed.
     * @throws RuntimeException   if an error occurred.
     */
    @SuppressWarnings("unchecked")
    void dumpAttributeToXml(AttributeMapping am, final Element classElem, final ClassMapping cm,
            final Object oldObject, final Object object, final EditOperation editOperation)
            throws RuntimeException {
        Namespace namespace = Namespace.getNamespace(cm.getXmlNamespace());
        Namespace ns = namespace;
        String attrNs = am.getXmlNamespace();
        if (attrNs != null) {
            ns = Namespace.getNamespace(attrNs);
        }
        if (am.isReadOnly()) {
            // We do not stream read-only values out
            return;
        }
        if (am.getType() == AttributeMapping.Type.Class) {
            Path childPath = Path.fromString(cm.getXmlPath());
            PathSegment ps = new PathSegment(cm.getXmlNamespace(), cm.getXmlTag());
            childPath.addSegment(ps);
            ps = new PathSegment(am.getXmlNamespace(), am.getXmlTag());
            childPath.addSegment(ps);
            ClassMapping childClass = m_model.getClassMappingByPath(childPath);
            if (childClass == null) {
                throw new RuntimeException("Class mapping not found for path: " + childPath);
            }
            if (childClass.getType() == ClassMapping.Type.Union) {

                Object childValue = BeanUtil.getDirectFieldValue(am.getJavaMemberName(), object);
                if (am.isList()) {
                    if (editOperation == EditOperation.Merge && oldObject != null) {
                        List<Object> oldUnions = (List<Object>) BeanUtil.getDirectListValue(am.getJavaMemberName(),
                                oldObject);
                        for (Object oldUnion : oldUnions) {
                            Element unionAttrib = new Element(am.getXmlTag(), ns);
                            unionAttrib.setAttribute("operation", "delete");
                            // TODO: Optimize later - only dump out the index attributes
                            m_unionChoiceHelper.unionToXml(childClass, oldUnion, unionAttrib);
                            classElem.addContent(unionAttrib);
                        }
                    }
                    // Stick the new ones in
                    List<Object> unionList = (List<Object>) childValue;
                    for (Object unionObject : unionList) {
                        Element attrib = new Element(am.getXmlTag(), ns);
                        m_unionChoiceHelper.unionToXml(childClass, unionObject, attrib);
                        classElem.addContent(attrib);
                    }
                } else {
                    Element attrib = new Element(am.getXmlTag(), ns);
                    m_unionChoiceHelper.unionToXml(childClass, childValue, attrib);
                    classElem.addContent(attrib);
                }
            } else if (childClass.getType() == ClassMapping.Type.Choice) {
                Object choiceValue = BeanUtil.getDirectFieldValue(am.getJavaMemberName(), object);
                m_unionChoiceHelper.choiceToXml(cm, childClass, choiceValue, classElem, editOperation);
            } else if (!am.isList()) {
                boolean add = false;
                Object value = BeanUtil.getDirectFieldValue(am.getJavaMemberName(), object);
                Element childClassElem = new Element(childClass.getXmlTag(),
                        Namespace.getNamespace(childClass.getXmlNamespace()));
                if (value != null) {
                    childClassElem.setAttribute("operation", "replace");
                    add = true;
                    dumpAttributesToXml(childClassElem, childClass, null, value, EditOperation.NotApplicable);
                } else {
                    // Remove the child if there used to be one there originally
                    if (editOperation == EditOperation.Merge && oldObject != null) {
                        Object oldValue = BeanUtil.getDirectFieldValue(am.getJavaMemberName(), oldObject);
                        if (oldValue != null) {
                            childClassElem.setAttribute("operation", "delete");
                            add = true;
                        }
                    }
                }
                if (add) {
                    classElem.addContent(childClassElem);
                }
            } else {
                // Need to delete the old objects first (if there is an old object)
                if (oldObject != null) {
                    List<Object> oldVos = (List<Object>) BeanUtil.getDirectListValue(am.getJavaMemberName(),
                            oldObject);
                    for (Object vo : oldVos) {
                        Element childClassElem = new Element(childClass.getXmlTag(),
                                Namespace.getNamespace(childClass.getXmlNamespace()));
                        classElem.addContent(childClassElem);
                        childClassElem.setAttribute("operation", "delete");
                        // TODO: Optimize later - only dump out the index attributes
                        dumpAttributesToXml(childClassElem, childClass, null, vo, EditOperation.Create);
                    }
                }
                // Then, add the current ones - aka "create"
                List<Object> vos = (List<Object>) BeanUtil.getDirectListValue(am.getJavaMemberName(), object);
                for (Object vo : vos) {
                    Element childClassElem = new Element(childClass.getXmlTag(),
                            Namespace.getNamespace(childClass.getXmlNamespace()));
                    classElem.addContent(childClassElem);
                    childClassElem.setAttribute("operation", "replace");
                    dumpAttributesToXml(childClassElem, childClass, null, vo, EditOperation.Create);
                }
            }
        } else {
            // Check for a list here, too (this corresponds to a leaf-list)
            if (am.isList()) {
                // Delete all the existing leaf-list attributes if the operation is an edit
                if (editOperation == EditOperation.Merge && oldObject != null) {
                    List<Object> oldPrimitives = (List<Object>) BeanUtil.getDirectListValue(am.getJavaMemberName(),
                            oldObject);
                    for (Object prim : oldPrimitives) {
                        Element attrib = new Element(am.getXmlTag(), ns);
                        attrib.setAttribute("operation", "delete");
                        String strVal = am.convertValueToString(prim);
                        if (strVal != null) {
                            attrib.setText(strVal);
                        }
                        classElem.addContent(attrib);
                    }
                }
                // Stick the new ones in
                List<Object> primitives = (List<Object>) BeanUtil.getDirectListValue(am.getJavaMemberName(),
                        object);
                for (Object prim : primitives) {
                    Element attrib = new Element(am.getXmlTag(), ns);
                    String strVal = am.convertValueToString(prim);
                    if (strVal != null) {
                        attrib.setText(strVal);
                    }
                    classElem.addContent(attrib);
                }
            } else {
                Element targetNode = classElem;
                boolean add = false;
                Element attrib = null;
                ;
                if (am.isSynthetic() && am.getType() != AttributeMapping.Type.Primitive_Boolean) {
                    // No extra XML node here - just set the content directly (unless it is "empty")
                    attrib = classElem;
                    targetNode = null;
                } else {
                    attrib = new Element(am.getXmlTag(), ns);
                }
                Object value = BeanUtil.getDirectFieldValue(am.getJavaMemberName(), object);
                if (value != null) {
                    String strVal = am.convertValueToString(value);
                    if (strVal != null) {
                        attrib.setText(strVal);
                        add = true;
                    }
                } else {
                    // "Unset" the attribute by setting delete = true
                    if (editOperation == EditOperation.Merge && oldObject != null) {
                        Object oldValue = BeanUtil.getDirectFieldValue(am.getJavaMemberName(), oldObject);
                        if (oldValue != null) {
                            attrib.setAttribute("operation", "delete");
                            add = true;
                        }
                    }
                }
                if (add && targetNode != null) {
                    targetNode.addContent(attrib);
                }
            }
        }
    }

    /**
     * Give the XML node that represents a response from a NETCONF device, extracts and creates the list of POJOs that
     * correspond to the specified Java class.
     *
     * @param root                Root node of NETCONF XML returned from a query.
     * @param javaClass           Fully qualified class name of POJO.
     * @return                    List of objects of type 'javaClass', that are contained within the specified node.
     * @throws RuntimeException   if an error occurred.
     */
    public List<Object> fromXml(final Element root, final String javaClass) throws RuntimeException {
        ClassMapping cm = m_model.getClassMappingByName(javaClass);
        if (cm == null) {
            throw new RuntimeException("Class mapping not found: " + javaClass);
        }
        return fromXml(root, cm, false);
    }

    /**
     * Give the XML node that represents a response from a NETCONF device, extracts and creates the list of POJOs that
     * correspond to the specified Java class.
     *
     * @param root                Root node of NETCONF XML returned from a query.
     * @param cm                  Class mapping that represents the desired objects.
     * @param ignoreContainment   true to ignore intermediate containers, false to take them into account.
     * @return                    List of objects of the desired type, that are contained within the specified node.
     * @throws RuntimeException   if an error occurred.
     */
    @SuppressWarnings("unchecked")
    private List<Object> fromXml(final Element root, ClassMapping cm, final boolean ignoreContainment)
            throws RuntimeException {
        List<Object> ret = new ArrayList<Object>();
        final Path xmlpath = Path.fromString(cm.getXmlPath());
        final String namespace = cm.getXmlNamespace();
        if (xmlpath == null) {
            throw new RuntimeException("XML container not found for class: " + cm.getJavaClassName());
        }
        Element elementToProcess = root;
        if (!ignoreContainment) {
            Element current = root;
            // We are searching from the ROOT node for this class
            // final String[] xpathTokens = StringUtils.tokenizeToStringArray(xmlpath, "/");
            final ArrayList<PathSegment> xpathTokens = xmlpath.getSegments();
            int index = 0;
            //for (String token : xpathTokens)
            for (PathSegment token : xpathTokens) {
                current = current.getChild(token.getTag(), Namespace.getNamespace(token.getNamespaceUri()));
                if (current == null) {
                    // We have gotten only through part of the hierarchy (after which it has apparently ended).
                    // We take this to mean there is no data for the required objects; so return the empty list
                    //throw new RuntimeException("Unexpected XML tag - expected: " + token.getTag() + ", got: null");
                    s_logger.warn("Unexpected XML tag - expected: " + token.getTag() + ", got: null");
                    return ret;
                }
                index++;
            }
            elementToProcess = current;
        }
        // Now we are pointing to the PARENT node of the class we are interested in
        // Get the list of classes that we are looking for
        String tag = cm.getXmlTag();
        List<Element> classes = (List<Element>) elementToProcess.getChildren(tag,
                Namespace.getNamespace(namespace));
        for (Element theClass : classes) {
            //VersionedObject o = ModelUtil.getInstance().createObject(m_model.getModuleName()+"/"+cd.getName(), m_version);
            Object o = null;
            try {
                Class<?> cls = Class.forName(cm.getJavaClassName());
                o = cls.newInstance();
            } catch (final Exception ex) {
                throw new RuntimeException(
                        "Error instantiating class: " + cm.getJavaClassName() + " -- " + ex.getMessage());
            }
            setAttributesFromXml(o, theClass, cm);
            ret.add(o);
        }
        return ret;
    }

    /**
     * Processes the specified element and extracts all the attribute information from it; then sets these attributes
     * in the specified instance of the class represented by its mapping.
     * 
     * @param vo               POJO whose attributes are being converted from their XML representations.
     * @param classElement     Root XML node representing the instance.
     * @param cm               Class mapping that contains metadata about the class and its attributes.
     */
    @SuppressWarnings("unchecked")
    void setAttributesFromXml(final Object vo, final Element classElement, final ClassMapping cm) {
        //Namespace namespace = Namespace.getNamespace(cm.getXmlNamespace());
        Element elementToProcess = classElement;
        for (AttributeMapping am : cm.getAttributeMappings()) {
            Namespace attribNs = Namespace.getNamespace(am.getXmlNamespace());
            if (am.getType() == AttributeMapping.Type.Class) {
                Path childXmlPath = Path.fromString(cm.getXmlPath());
                PathSegment ps = new PathSegment(cm.getXmlNamespace(), cm.getXmlTag());
                PathSegment attrPs = new PathSegment(am.getXmlNamespace(), am.getXmlTag());
                childXmlPath.addSegment(ps);
                childXmlPath.addSegment(attrPs);
                ClassMapping childClass = m_model.getClassMappingByPath(childXmlPath);
                if (childClass == null) {
                    throw new RuntimeException("Class mapping not found for path : " + childXmlPath);
                }
                if (childClass.getType() == ClassMapping.Type.Union) {
                    List<Element> attribElements = elementToProcess.getChildren(am.getXmlTag(), attribNs);
                    if (am.isList()) {
                        List<Object> listVal = new ArrayList<Object>();
                        for (Element attribElement : attribElements) {
                            Object unionValue = m_unionChoiceHelper.xmlToUnion(cm, attribElement);
                            listVal.add(unionValue);
                        }
                        BeanUtil.setDirectListValue(am.getJavaMemberName(), vo, listVal, "java.util.ArrayList");
                    } else {
                        Element attribElement = attribElements.get(0);
                        if (attribElement != null) {
                            Object unionValue = m_unionChoiceHelper.xmlToUnion(cm, attribElement);
                            BeanUtil.setDirectFieldValue(am.getJavaMemberName(), vo, unionValue);
                        }
                    }
                } else if (childClass.getType() == ClassMapping.Type.Choice) {
                    Object choiceClass = m_unionChoiceHelper.xmlToChoice(cm, am.getXmlTag(), classElement);
                    BeanUtil.setDirectFieldValue(am.getJavaMemberName(), vo, choiceClass);
                } else {
                    //  Pass in an empty xpathOid and ignore containment, since we are rooted right here
                    List<Object> kids = this.fromXml(elementToProcess, childClass, true);
                    if (kids != null) {
                        if (am.isList()) {
                            BeanUtil.setDirectListValue(am.getJavaMemberName(), vo, kids,
                                    childClass.getJavaClassName());
                        } else if (kids.size() > 0) {
                            BeanUtil.setDirectFieldValue(am.getJavaMemberName(), vo, kids.get(0));
                        }
                    }
                }
            } else {
                // Built-in, enumeration and user-defined
                if (am.isList()) {
                    // This is a leaf-list; get all the values and set the thing
                    List<Element> attribElements = elementToProcess.getChildren(am.getXmlTag(), attribNs);
                    List<Object> primitives = new ArrayList<Object>();
                    String primitiveClass = "java.lang.String";
                    for (Element attribElement : attribElements) {
                        Object value = am.convertStringToValue(attribElement.getText());
                        primitiveClass = value.getClass().getName();
                        primitives.add(value);
                    }
                    BeanUtil.setDirectListValue(am.getJavaMemberName(), vo, primitives, primitiveClass);
                } else {
                    Element sourceNode = elementToProcess;
                    if (!am.isSynthetic() || am.getType() == AttributeMapping.Type.Primitive_Boolean) {
                        Element attribElement = elementToProcess.getChild(am.getXmlTag(), attribNs);
                        if (attribElement == null) {
                            s_logger.debug("Attribute with tag: " + am.getXmlTag() + " not received.");
                            continue;
                        }
                        sourceNode = attribElement;
                    }
                    // This will take care of transformations from String to any primitive Java type
                    BeanUtil.setDirectFieldValue(am.getJavaMemberName(), vo,
                            am.convertStringToValue(sourceNode.getText()));
                }
            }
        }
    }

}