/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.services.extension; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class MigrationUtils { static final String EXTENSIONS_TAGNAME = "extensions"; //$NON-NLS-1$ static final String NO_COMPARATOR_TAGNAME = "no-comparator"; //$NON-NLS-1$ static final String DELETED_TAGNAME = "deleted"; //$NON-NLS-1$ static final String STRINGSET_ENTRY_TAGNAME = "string"; //$NON-NLS-1$ /** * Returns the value of the <uuid> element. * * @param doc the XML document. * * @throws MigrationException if there is no <uuid> element * in the given document, or there are multiple such elements. */ public static String getUuid(Document doc) throws MigrationException { Element element = getElementOfEntity(doc, "uuid"); //$NON-NLS-1$ if (element == null) { throw new MigrationException("Document contains no <uuid> element"); //$NON-NLS-1$ } return element.getTextContent(); } /** * Returns the root element of a given extension. * * @param doc the XML document. * @param extensionAlias the alias of the extension to retrieve. * @return the extension matching the given alias, or <code>null</code> * if no such extension exists. * * @throws MigrationException if there is more than one element matching * the given extension alias. */ public static Element getExtension(Document doc, String extensionAlias) throws MigrationException { Element extensionsElement = getElementOfEntity(doc, EXTENSIONS_TAGNAME); return extensionsElement != null ? getChild(extensionsElement, extensionAlias) : null; } /** * Returns the element of the entity with the given <code>elementName</code>. * * @param doc the XML document. * @param elementName the name of the element to retrieve. * @return the element matching the given name, or <code>null</code> * if no such element exists. * * @throws MigrationException if the entity has more than one * element matching the given element name. */ public static Element getElementOfEntity(Document doc, String elementName) throws MigrationException { return getChild(doc.getDocumentElement(), elementName); } /** * Returns the element of an extension with the given <code>elementName</code>. * * @param doc the XML document. * @param extensionAlias the alias of an extension. * @param elementName the name of the element to retrieve. * * @return the element matching the given name, or <code>null</code> * if no such element exists. * * @throws MigrationException if the extension has more than one * element matching the given element name. */ public static Element getElementOfExtension(Document doc, String extensionAlias, String elementName) throws MigrationException { Element extensionElement = getExtension(doc, extensionAlias); return extensionElement != null ? getChild(extensionElement, elementName) : null; } /** * Returns the child element with the given <code>elementName</code> of the * specified parent element. * * @param parentElement the parent element to search for a certain child element. * @param elementName the name of the element to retrieve. * * @return the element matching the given name, or <code>null</code> if no * such element exists. * * @throws MigrationException if the parent element has more than one * child element matching the given name. */ public static Element getChild(Element parentElement, String elementName) throws MigrationException { if (parentElement== null) { throw new IllegalArgumentException("argument 'parentElement' must not be null"); //$NON-NLS-1$ } int count = 0; Element result = null; NodeList nodes = parentElement.getChildNodes(); for (int i = 0; i < nodes.getLength(); ++i) { Node child = nodes.item(i); if (matches(child, elementName)) { result = (Element) child; ++count; } } if (count > 1) { throwInvalidNumberOfElementsFound(parentElement.getNodeName(), elementName, 1, count); } return result; } /** * Returns the <tt>extensions</tt> element of the entity, or <code>null</code> * if there is no such element. * * @param doc the XML document. * @throws MigrationException if the entity has more than one * <tt>extensions</tt> element. */ public static Element getExtensionsNode(Document doc) throws MigrationException { return getElementOfEntity(doc, EXTENSIONS_TAGNAME); } /** * Returns a list of extension elements matching the given aliases. * * @param doc the XML document. * @param aliases the aliases to search for. * @return a list of extension elements, or an empty list. * * @throws MigrationException if the entity has more than one * <tt>extensions</tt> element. */ public static List<Element> getExtensions(Document doc, Set<String> aliases) throws MigrationException { List<Element> result = new ArrayList<Element>(); Element extensions = getElementOfEntity(doc, EXTENSIONS_TAGNAME); if (extensions == null) { return result; } NodeList nodes = extensions.getChildNodes(); for (int i = 0; i < nodes.getLength(); ++i) { Node node = nodes.item(i); String name = node.getNodeName(); if (aliases.contains(name)) { result.add((Element) node); } } return result; } /** * Returns the <tt>extensions</tt> element of the entity. If the element does * not exist, it is created and appended to the entity root element. * * @param doc the XML document. * @return the <tt>extensions</tt> element of the entity. * * @throws MigrationException if the entity has more than one * <tt>extensions</tt> element. */ public static Element getOrCreateExtensionsNode(Document doc) throws MigrationException { Element extensionsElement = getElementOfEntity(doc, EXTENSIONS_TAGNAME); if (extensionsElement == null) { extensionsElement = doc.createElement(EXTENSIONS_TAGNAME); Element nocompElement = doc.createElement(NO_COMPARATOR_TAGNAME); extensionsElement.appendChild(nocompElement); Element entity = doc.getDocumentElement(); entity.appendChild(extensionsElement); } return extensionsElement; } /** * Returns the root element of the given extension. If there is not yet an element * for the extension, a new element is created and attached to the <tt><extensions></tt> * element of the entity. * * @param doc the XML document. * @param extensionAlias the alias of the extension to retrieve or create. * * @throws MigrationException if the entity has more than one * extension matching the given alias. */ public static Element getOrCreateExtensionNode(Document doc, String extensionAlias) throws MigrationException { Element extensionElement = getExtension(doc, extensionAlias); if (extensionElement == null) { Element extensionsElement = getOrCreateExtensionsNode(doc); extensionElement = doc.createElement(extensionAlias); Element deletedElement = doc.createElement(DELETED_TAGNAME); deletedElement.setTextContent(Boolean.FALSE.toString()); extensionElement.appendChild(deletedElement); extensionsElement.appendChild(extensionElement); } return extensionElement; } /** * Migrates a string-like node of an enity to a set-like node. * Note that the set-like node is only created, if the string-like node exists. * The value of the string-like node is added as first entry to the set-like node. * * @param doc the XML document. * @param elementName the name of the string-like node. * @param setElementName the name of the set-like node after migration. * @param noComparator * if <code>true</code>, a <no-comparator/> element is added to * the set-like node. * * @throws MigrationException if the entity has more than one * element matching the given <code>elementName</code>. * */ public static void migrateStringToStringSet(Document doc, String elementName, String setElementName, boolean noComparator) throws MigrationException { Element element = getElementOfEntity(doc, elementName); migrateStringToStringSet(doc, element, setElementName, noComparator); } /** * Migrates a string-like node of an extension to a set-like node. * Note that the set-like node is only created, if the string-like node exists. * The value of the string-like node is added as first entry to the set-like node. * * @param doc the XML document. * @param extensionAlias the alias of an extension. * @param elementName the name of the string-like node. * @param setElementName the name of the set-like node after migration. * @param noComparator * if <code>true</code>, a <no-comparator/> node is added to * the set-like node. * * @throws MigrationException if the extension has more than one * element matching the given <code>elementName</code>. */ public static void migrateStringToStringSet(Document doc, String extensionAlias, String elementName, String setElementName, boolean noComparator) throws MigrationException { Element stringElement = getElementOfExtension(doc, extensionAlias, elementName); migrateStringToStringSet(doc, stringElement, setElementName, noComparator); } private static void migrateStringToStringSet(Document doc, Element stringElement, String setElementName, boolean noComparator) { if (stringElement != null) { String value = stringElement.getTextContent(); Node parent = stringElement.getParentNode(); parent.removeChild(stringElement); Element setElement = doc.createElement(setElementName); if (noComparator) { Element setEntry = doc.createElement(NO_COMPARATOR_TAGNAME); setElement.appendChild(setEntry); } Element setEntry = doc.createElement(STRINGSET_ENTRY_TAGNAME); setEntry.setTextContent(value); setElement.appendChild(setEntry); parent.appendChild(setElement); } } /** * Renames an element of an entity. * * @param doc the XML document. * @param elementName the name of the element to rename. * @param newElementName the new name of the element. * * @throws MigrationException if the entity has more than one * element matching the given <code>elementName</code>. */ public static void renameTag(Document doc, String elementName, String newElementName) throws MigrationException { Element element = getElementOfEntity(doc, elementName); renameTag(doc, element, newElementName); } /** * Renames an element of an extension. * * @param doc the XML document. * @param extensionAlias the alias of an extension. * @param elementName the name of the element to rename. * @param newElementName the new name of the element. * * @throws MigrationException if the extension has more than one * element matching the given <code>elementName</code>. */ public static void renameTag(Document doc, String extensionAlias, String elementName, String newElementName) throws MigrationException { Element element = getElementOfExtension(doc, extensionAlias, elementName); renameTag(doc, element, newElementName); } /** * Renames an element. * * @param doc the XML document. * @param element the lement to rename. * @param newTagName the new name of the element. */ public static void renameTag(Document doc, Element element, String newTagName) { if (element != null) { Node parent = element.getParentNode(); parent.removeChild(element); Element newElement = doc.createElement(newTagName); parent.appendChild(newElement); NodeList children = element.getChildNodes(); cloneNodes(newElement, children); cloneAttributes(element, newElement); } } /** * Renames all tags with the given old tag name. * @param doc the XML document. * @param tagName the tag name to search for. * @param newTagName the new name of the tags. */ public static void renameAllTags(Document doc, String tagName, String newTagName) { NodeList nodes = doc.getElementsByTagName(tagName); while (nodes.getLength() > 0) { Element elem = (Element) nodes.item(0); renameTag(doc, elem, newTagName); nodes = doc.getElementsByTagName(tagName); } } /** * Removes all tags with the given tag name. * @param doc the XML document. * @param tagName the tag name to search for. */ public static void removeAllTags(Document doc, String tagName) { NodeList nodes = doc.getElementsByTagName(tagName); while (nodes.getLength() > 0) { Element elem = (Element) nodes.item(0); elem.getParentNode().removeChild(elem); } } /** * Moves an element of the entity to an extension. * * @param doc the XML document. * @param targetExtensionAlias the alias of the target extension. * @param elementName the name of the element to move. * * @throws MigrationException if the entity has more than one * extension matching the given alias or more than one * element matches the given <code>elementName</code>. */ public static void moveTagToExtension(Document doc, String targetExtensionAlias, String elementName) throws MigrationException { cloneNodes(doc, null, targetExtensionAlias, elementName, elementName, true); } /** * Moves an element of the entity to an extension and renames it. * * @param doc the XML document. * @param targetExtensionAlias the alias of the target extension. * @param sourceElementName the name of the element to move. * @param targetElementName the new name of the element. * * @throws MigrationException if the entity has more than one * extension matching the given alias or more than one * element matches the given <code>sourceElementName</code>. */ public static void moveTagToExtension(Document doc, String targetExtensionAlias, String sourceElementName, String targetElementName) throws MigrationException { cloneNodes(doc, null, targetExtensionAlias, sourceElementName, targetElementName, true); } /** * Moves an element from one extension to another and renames it. * * @param doc the XML document. * @param sourceExtensionAlias the alias of the extension with the element to be moved. * @param targetExtensionAlias the alias of the extension to which the element should be moved. * @param sourceElementName the name of the element to move. * @param targetElementName the new name of the element. * * @throws MigrationException if the entity has more than one * extension matching the given alias or the source extension has more * than one element matches the given <code>sourceElementName</code>. */ public static void moveTagToExtension(Document doc, String sourceExtensionAlias, String targetExtensionAlias, String sourceElementName, String targetElementName) throws MigrationException { cloneNodes(doc, sourceExtensionAlias, targetExtensionAlias, sourceElementName, targetElementName, true); } /** * Copies an element of the entity to an extension. * * @param doc the XML document. * @param targetExtensionAlias the alias of the target extension. * @param elementName the name of the element to copy. * * @throws MigrationException if the entity has more than one * extension matching the given alias or more than one * element matches the given <code>elementName</code>. */ public static void copyTagToExtension(Document doc, String targetExtensionAlias, String elementName) throws MigrationException { cloneNodes(doc, null, targetExtensionAlias, elementName, elementName, false); } /** * Copies an element of the entity to an extension and renames it. * * @param doc the XML document. * @param targetExtensionAlias the alias of the target extension. * @param sourceElementName the name of the element to copy. * @param targetElementName the new name of the element. * * @throws MigrationException if the entity has more than one * extension matching the given alias or more than one * element matches the given <code>sourceElementName</code>. */ public static void copyTagToExtension(Document doc, String targetExtensionAlias, String sourceElementName, String targetElementName) throws MigrationException { cloneNodes(doc, null, targetExtensionAlias, sourceElementName, targetElementName, false); } private static void cloneNodes(Document doc, String sourceExtensionAlias, String targetExtensionAlias, String sourceElementName, String targetElementName, boolean moveElement) throws MigrationException { Element sourceElement = sourceExtensionAlias != null ? getElementOfExtension(doc, sourceExtensionAlias, sourceElementName) : getElementOfEntity(doc, sourceElementName); if (sourceElement != null) { Element targetExtensionElement = getOrCreateExtensionNode(doc, targetExtensionAlias); Element targetElement = doc.createElement(targetElementName); targetExtensionElement.appendChild(targetElement); Node clonedSourceNode = sourceElement.cloneNode(true); cloneNodes(targetElement, clonedSourceNode.getChildNodes()); if (moveElement) { Node parent = sourceElement.getParentNode(); parent.removeChild(sourceElement); } } } private static void cloneNodes(Element targetElement, NodeList nodes) { if (nodes != null) { for (int i = 0; i < nodes.getLength(); i++) { targetElement.appendChild(nodes.item(i).cloneNode(true)); } } } private static void cloneAttributes(Element element, Element targetElement) { NamedNodeMap attributes = element.getAttributes(); for (int i = 0; i < attributes.getLength(); ++i) { Node attribute = attributes.item(i); targetElement.setAttribute(attribute.getNodeName(), attribute.getTextContent()); } } private static boolean matches(Node node, String name) { return (node != null) && (node instanceof Element) && (name.equals(node.getNodeName())); } private static void throwInvalidNumberOfElementsFound(String parentName, String elementName, int expected, int found) throws MigrationException { throw new MigrationException(MessageFormat.format( "Unexpected number of <{0}> elements found inside <{1}> (expected: {2}, found: {3})", elementName, parentName, expected, found)); } }