org.xwiki.extension.repository.internal.DefaultExtensionSerializer.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.extension.repository.internal.DefaultExtensionSerializer.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.extension.repository.internal;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.IOUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xwiki.component.annotation.Component;
import org.xwiki.extension.AbstractExtension;
import org.xwiki.extension.DefaultExtensionAuthor;
import org.xwiki.extension.DefaultExtensionDependency;
import org.xwiki.extension.DefaultExtensionIssueManagement;
import org.xwiki.extension.DefaultExtensionScm;
import org.xwiki.extension.DefaultExtensionScmConnection;
import org.xwiki.extension.Extension;
import org.xwiki.extension.ExtensionAuthor;
import org.xwiki.extension.ExtensionDependency;
import org.xwiki.extension.ExtensionId;
import org.xwiki.extension.ExtensionIssueManagement;
import org.xwiki.extension.ExtensionLicense;
import org.xwiki.extension.ExtensionLicenseManager;
import org.xwiki.extension.ExtensionScm;
import org.xwiki.extension.ExtensionScmConnection;
import org.xwiki.extension.InstalledExtension;
import org.xwiki.extension.InvalidExtensionException;
import org.xwiki.extension.repository.internal.core.DefaultCoreExtension;
import org.xwiki.extension.repository.internal.core.DefaultCoreExtensionRepository;
import org.xwiki.extension.repository.internal.local.DefaultLocalExtension;
import org.xwiki.extension.repository.internal.local.DefaultLocalExtensionRepository;
import org.xwiki.extension.version.internal.DefaultVersionConstraint;

/**
 * Local repository storage serialization tool.
 *
 * @version $Id: 95ee143649004d4eb32b619d4232fe2459a9326f $
 * @since 6.4M1
 */
@Component
@Singleton
public class DefaultExtensionSerializer implements ExtensionSerializer {
    private static final String ELEMENT_ID = "id";

    private static final String ELEMENT_VERSION = "version";

    private static final String ELEMENT_TYPE = "type";

    private static final String ELEMENT_LICENSES = "licenses";

    private static final String ELEMENT_LLICENSE = "license";

    private static final String ELEMENT_LLNAME = "name";

    private static final String ELEMENT_LLCONTENT = "content";

    private static final String ELEMENT_NAME = "name";

    private static final String ELEMENT_SUMMARY = "summary";

    private static final String ELEMENT_CATEGORY = "category";

    private static final String ELEMENT_DESCRIPTION = "description";

    private static final String ELEMENT_WEBSITE = "website";

    private static final String ELEMENT_AUTHORS = "authors";

    private static final String ELEMENT_AAUTHOR = "author";

    private static final String ELEMENT_AANAME = "name";

    private static final String ELEMENT_AAURL = "url";

    private static final String ELEMENT_DEPENDENCIES = "dependencies";

    private static final String ELEMENT_DDEPENDENCY = "dependency";

    private static final String ELEMENT_FEATURES = "features";

    private static final String ELEMENT_FFEATURE = "feature";

    private static final String ELEMENT_SCM = "scm";

    private static final String ELEMENT_SCONNECTION = "connection";

    private static final String ELEMENT_SDEVELOPERCONNECTION = "developerconnection";

    private static final String ELEMENT_SCSYSTEM = "system";

    private static final String ELEMENT_SCPATH = "path";

    private static final String ELEMENT_SURL = "url";

    private static final String ELEMENT_ISSUEMANAGEMENT = "issuemanagement";

    private static final String ELEMENT_ISYSTEM = "system";

    private static final String ELEMENT_IURL = "url";

    private static final String ELEMENT_PROPERTIES = "properties";

    @Deprecated
    private static final String ELEMENT_INSTALLED = "installed";

    @Deprecated
    private static final String ELEMENT_NAMESPACES = "namespaces";

    @Deprecated
    private static final String ELEMENT_NNAMESPACE = "namespace";

    @Inject
    private ExtensionLicenseManager licenseManager;

    /**
     * Used to parse XML descriptor file.
     */
    private DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

    protected Map<String, ExtensionPropertySerializer> serializerById;

    protected Map<Class<?>, ExtensionPropertySerializer> serializerByClass;

    {
        {
            this.serializerById = new HashMap<String, ExtensionPropertySerializer>();
            this.serializerByClass = new LinkedHashMap<Class<?>, ExtensionPropertySerializer>();

            StringExtensionPropertySerializer stringSerializer = new StringExtensionPropertySerializer();
            IntegerExtensionPropertySerializer integerSerializer = new IntegerExtensionPropertySerializer();
            BooleanExtensionPropertySerializer booleanSerializer = new BooleanExtensionPropertySerializer();
            DateExtensionPropertySerializer dateSerializer = new DateExtensionPropertySerializer();
            URLExtensionPropertySerializer urlSerializer = new URLExtensionPropertySerializer();
            CollectionExtensionPropertySerializer collectionSerializer = new CollectionExtensionPropertySerializer(
                    this.serializerById, this.serializerByClass);
            SetExtensionPropertySerializer setSerializer = new SetExtensionPropertySerializer(this.serializerById,
                    this.serializerByClass);
            StringKeyMapExtensionPropertySerializer mapSerializer = new StringKeyMapExtensionPropertySerializer(
                    this.serializerById, this.serializerByClass);

            this.serializerById.put(null, stringSerializer);
            this.serializerById.put("", stringSerializer);
            this.serializerById.put(integerSerializer.getType(), integerSerializer);
            this.serializerById.put(booleanSerializer.getType(), booleanSerializer);
            this.serializerById.put(dateSerializer.getType(), dateSerializer);
            this.serializerById.put(urlSerializer.getType(), urlSerializer);
            this.serializerById.put(collectionSerializer.getType(), collectionSerializer);
            this.serializerById.put(setSerializer.getType(), setSerializer);
            this.serializerById.put(mapSerializer.getType(), mapSerializer);

            this.serializerByClass.put(String.class, stringSerializer);
            this.serializerByClass.put(Integer.class, integerSerializer);
            this.serializerByClass.put(Boolean.class, booleanSerializer);
            this.serializerByClass.put(Date.class, dateSerializer);
            this.serializerByClass.put(URL.class, urlSerializer);
            this.serializerByClass.put(Set.class, setSerializer);
            this.serializerByClass.put(Collection.class, collectionSerializer);
            this.serializerByClass.put(Map.class, mapSerializer);
        }
    }

    @Override
    public DefaultCoreExtension loadCoreExtensionDescriptor(DefaultCoreExtensionRepository repository, URL url,
            InputStream descriptor) throws InvalidExtensionException {
        Element extensionElement = getExtensionElement(descriptor);

        DefaultCoreExtension coreExtension = new DefaultCoreExtension(repository, url,
                getExtensionId(extensionElement), getExtensionType(extensionElement));

        loadExtensionDescriptor(coreExtension, extensionElement);

        return coreExtension;
    }

    @Override
    public DefaultLocalExtension loadLocalExtensionDescriptor(DefaultLocalExtensionRepository repository,
            InputStream descriptor) throws InvalidExtensionException {
        Element extensionElement = getExtensionElement(descriptor);

        DefaultLocalExtension localExtension = new DefaultLocalExtension(repository,
                getExtensionId(extensionElement), getExtensionType(extensionElement));

        loadExtensionDescriptor(localExtension, extensionElement);

        return localExtension;
    }

    private Element getExtensionElement(InputStream descriptor) throws InvalidExtensionException {
        DocumentBuilder documentBuilder;
        try {
            documentBuilder = this.documentBuilderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new InvalidExtensionException("Failed to create new DocumentBuilder", e);
        }

        Document document;
        try {
            document = documentBuilder.parse(descriptor);
        } catch (Exception e) {
            throw new InvalidExtensionException("Failed to parse descriptor", e);
        }

        return document.getDocumentElement();
    }

    private ExtensionId getExtensionId(Element extensionElement) {
        Node idNode = extensionElement.getElementsByTagName(ELEMENT_ID).item(0);
        Node versionNode = extensionElement.getElementsByTagName(ELEMENT_VERSION).item(0);

        return new ExtensionId(idNode.getTextContent(), versionNode.getTextContent());
    }

    private String getExtensionType(Element extensionElement) {
        Node typeNode = extensionElement.getElementsByTagName(ELEMENT_TYPE).item(0);

        return typeNode.getTextContent();
    }

    private void loadExtensionDescriptor(AbstractExtension extension, Element extensionElement)
            throws InvalidExtensionException {
        Node nameNode = getNode(extensionElement, ELEMENT_NAME);
        if (nameNode != null) {
            extension.setName(nameNode.getTextContent());
        }
        Node categoryNode = getNode(extensionElement, ELEMENT_CATEGORY);
        if (categoryNode != null) {
            extension.setCategory(categoryNode.getTextContent());
        }
        Node summaryNode = getNode(extensionElement, ELEMENT_SUMMARY);
        if (summaryNode != null) {
            extension.setSummary(summaryNode.getTextContent());
        }
        Node descriptionNode = getNode(extensionElement, ELEMENT_DESCRIPTION);
        if (descriptionNode != null) {
            extension.setDescription(descriptionNode.getTextContent());
        }
        Node websiteNode = getNode(extensionElement, ELEMENT_WEBSITE);
        if (websiteNode != null) {
            extension.setWebsite(websiteNode.getTextContent());
        }

        // Licenses
        Node licensesNode = getNode(extensionElement, ELEMENT_LICENSES);
        if (licensesNode != null) {
            NodeList licenseNodeList = licensesNode.getChildNodes();
            for (int i = 0; i < licenseNodeList.getLength(); ++i) {
                Node licenseNode = licenseNodeList.item(i);

                if (licenseNode.getNodeName().equals(ELEMENT_LLICENSE)) {
                    Node licenseNameNode = getNode(licenseNode, ELEMENT_LLNAME);
                    Node licenceContentNode = getNode(licenseNode, ELEMENT_LLCONTENT);

                    String licenseName = licenseNameNode.getTextContent();
                    ExtensionLicense license = this.licenseManager.getLicense(licenseName);
                    if (license == null) {
                        try {
                            license = new ExtensionLicense(licenseName,
                                    licenceContentNode != null
                                            ? IOUtils.readLines(
                                                    new StringReader(licenceContentNode.getTextContent()))
                                            : null);
                        } catch (IOException e) {
                            // That should never happen
                            throw new InvalidExtensionException("Failed to write license content", e);
                        }
                    }

                    extension.addLicense(license);
                }
            }
        }

        // Authors
        Node authorsNode = getNode(extensionElement, ELEMENT_AUTHORS);
        if (authorsNode != null) {
            NodeList authors = authorsNode.getChildNodes();
            for (int i = 0; i < authors.getLength(); ++i) {
                Node authorNode = authors.item(i);

                if (authorNode.getNodeName().equals(ELEMENT_AAUTHOR)) {
                    Node authorNameNode = getNode(authorNode, ELEMENT_AANAME);
                    Node authorURLNode = getNode(authorNode, ELEMENT_AAURL);

                    String authorName = authorNameNode != null ? authorNameNode.getTextContent() : null;
                    URL authorURL;
                    if (authorURLNode != null) {
                        try {
                            authorURL = new URL(authorURLNode.getTextContent());
                        } catch (MalformedURLException e) {
                            // That should never happen
                            throw new InvalidExtensionException(
                                    "Malformed URL [" + authorURLNode.getTextContent() + "]", e);
                        }
                    } else {
                        authorURL = null;
                    }

                    extension.addAuthor(new DefaultExtensionAuthor(authorName, authorURL));
                }
            }
        }

        // Features
        List<String> features = parseList(extensionElement, ELEMENT_FEATURES, ELEMENT_FFEATURE);
        if (features != null) {
            extension.setFeatures(features);
        }

        // Scm
        extension.setScm(loadlScm(extensionElement));

        // Issue Management
        extension.setIssueManagement(loadIssueManagement(extensionElement));

        // Dependencies
        Node dependenciesNode = getNode(extensionElement, ELEMENT_DEPENDENCIES);
        if (dependenciesNode != null) {
            NodeList dependenciesNodeList = dependenciesNode.getChildNodes();
            for (int i = 0; i < dependenciesNodeList.getLength(); ++i) {
                Node dependency = dependenciesNodeList.item(i);

                if (dependency.getNodeName().equals(ELEMENT_DDEPENDENCY)) {
                    Node dependencyIdNode = getNode(dependency, ELEMENT_ID);
                    Node dependencyVersionNode = getNode(dependency, ELEMENT_VERSION);

                    extension.addDependency(new DefaultExtensionDependency(dependencyIdNode.getTextContent(),
                            new DefaultVersionConstraint(dependencyVersionNode.getTextContent()),
                            parseProperties((Element) dependency)));
                }
            }
        }

        // Properties
        Map<String, Object> properties = parseProperties(extensionElement);
        if (properties != null) {
            extension.setProperties(properties);
        }

        // Deprecated Install fields

        Node enabledNode = getNode(extensionElement, ELEMENT_INSTALLED);
        if (enabledNode != null) {
            extension.putProperty(InstalledExtension.PKEY_INSTALLED, Boolean.valueOf(enabledNode.getTextContent()));
        }

        // Deprecated Namespaces
        List<String> namespaces = parseList(extensionElement, ELEMENT_NAMESPACES, ELEMENT_NNAMESPACE);
        if (namespaces != null) {
            extension.putProperty(InstalledExtension.PKEY_NAMESPACES, namespaces);
        }
    }

    private ExtensionScm loadlScm(Element extensionElement) {
        Node node = getNode(extensionElement, ELEMENT_SCM);

        if (node != null) {
            Node connectionNode = getNode(node, ELEMENT_SCONNECTION);
            Node developerConnectionNode = getNode(node, ELEMENT_SDEVELOPERCONNECTION);
            Node urlNode = getNode(node, ELEMENT_SURL);

            return new DefaultExtensionScm(urlNode != null ? urlNode.getTextContent() : null,
                    loadlScmConnection(connectionNode), loadlScmConnection(developerConnectionNode));
        }

        return null;
    }

    private ExtensionScmConnection loadlScmConnection(Node scmConnectionElement) {
        if (scmConnectionElement != null) {
            Node system = getNode(scmConnectionElement, ELEMENT_SCSYSTEM);
            Node path = getNode(scmConnectionElement, ELEMENT_SCPATH);

            if (system != null) {
                return new DefaultExtensionScmConnection(system.getTextContent(),
                        path != null ? path.getTextContent() : null);
            }
        }

        return null;
    }

    private ExtensionIssueManagement loadIssueManagement(Element extensionElement) {
        Node node = getNode(extensionElement, ELEMENT_ISSUEMANAGEMENT);

        if (node != null) {
            Node systemNode = getNode(node, ELEMENT_ISYSTEM);
            Node urlNode = getNode(node, ELEMENT_IURL);

            if (systemNode != null) {
                return new DefaultExtensionIssueManagement(systemNode.getTextContent(),
                        urlNode != null ? urlNode.getTextContent() : null);
            }
        }

        return null;
    }

    private List<String> parseList(Element extensionElement, String rootElement, String childElement) {
        List<String> list;

        Node featuresNode = getNode(extensionElement, rootElement);
        if (featuresNode != null) {
            list = new LinkedList<String>();

            NodeList features = featuresNode.getChildNodes();
            for (int i = 0; i < features.getLength(); ++i) {
                Node featureNode = features.item(i);

                if (featureNode.getNodeName() == childElement) {
                    list.add(featureNode.getTextContent());
                }
            }
        } else {
            list = null;
        }

        return list;
    }

    private Map<String, Object> parseProperties(Element parentElement) {
        Map<String, Object> properties = null;

        Node propertiesNode = getNode(parentElement, ELEMENT_PROPERTIES);
        if (propertiesNode != null) {
            properties = new HashMap<String, Object>();
            NodeList propertyNodeList = propertiesNode.getChildNodes();
            for (int i = 0; i < propertyNodeList.getLength(); ++i) {
                Node propertyNode = propertyNodeList.item(i);

                if (propertyNode.getNodeType() == Node.ELEMENT_NODE) {
                    Object value = CollectionExtensionPropertySerializer.toValue((Element) propertyNode,
                            this.serializerById);

                    if (value != null) {
                        properties.put(propertyNode.getNodeName(), value);
                    }
                }
            }
        }

        return properties;
    }

    private Node getNode(Node parentNode, String elementName) {
        NodeList children = parentNode.getChildNodes();
        for (int i = 0; i < children.getLength(); ++i) {
            Node node = children.item(i);

            if (node.getNodeName().equals(elementName)) {
                return node;
            }
        }

        return null;
    }

    @Override
    public void saveExtensionDescriptor(Extension extension, OutputStream fos)
            throws ParserConfigurationException, TransformerException {
        DocumentBuilder documentBuilder = this.documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.newDocument();

        Element extensionElement = document.createElement("extension");
        document.appendChild(extensionElement);

        addElement(document, extensionElement, ELEMENT_ID, extension.getId().getId());
        addElement(document, extensionElement, ELEMENT_VERSION, extension.getId().getVersion().getValue());
        addElement(document, extensionElement, ELEMENT_TYPE, extension.getType());
        addElement(document, extensionElement, ELEMENT_NAME, extension.getName());
        addElement(document, extensionElement, ELEMENT_CATEGORY, extension.getCategory());
        addElement(document, extensionElement, ELEMENT_SUMMARY, extension.getSummary());
        addElement(document, extensionElement, ELEMENT_DESCRIPTION, extension.getDescription());
        addElement(document, extensionElement, ELEMENT_WEBSITE, extension.getWebSite());

        addFeatures(document, extensionElement, extension);

        addAuthors(document, extensionElement, extension);

        addLicenses(document, extensionElement, extension);

        addScm(document, extensionElement, extension);

        addIssueManagement(document, extensionElement, extension);

        addDependencies(document, extensionElement, extension);

        addProperties(document, extensionElement, extension.getProperties());

        // save

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

        DOMSource source = new DOMSource(document);
        Result result = new StreamResult(fos);
        trans.transform(source, result);
    }

    private void addLicenses(Document document, Element parentElement, Extension extension) {
        if (extension.getLicenses() != null && !extension.getLicenses().isEmpty()) {
            Element licensesElement = document.createElement(ELEMENT_LICENSES);
            parentElement.appendChild(licensesElement);

            for (ExtensionLicense license : extension.getLicenses()) {
                Element licenseElement = document.createElement(ELEMENT_LLICENSE);
                licensesElement.appendChild(licenseElement);

                addElement(document, licenseElement, ELEMENT_LLNAME, license.getName());
                if (this.licenseManager.getLicense(license.getName()) == null && license.getContent() != null) {
                    // Only store content if it's a custom license (license content is pretty big generally)
                    StringWriter content = new StringWriter();
                    try {
                        IOUtils.writeLines(license.getContent(), IOUtils.LINE_SEPARATOR_UNIX, content);
                    } catch (IOException e) {
                        // That should never happen
                    }
                    addElement(document, licenseElement, ELEMENT_LLCONTENT, content.toString());
                }
            }
        }
    }

    private void addFeatures(Document document, Element parentElement, Extension extension) {
        Collection<String> features = extension.getFeatures();
        if (!features.isEmpty()) {
            Element featuresElement = document.createElement(ELEMENT_FEATURES);
            parentElement.appendChild(featuresElement);

            for (String feature : features) {
                addElement(document, featuresElement, ELEMENT_FFEATURE, feature);
            }
        }
    }

    private void addAuthors(Document document, Element parentElement, Extension extension) {
        Collection<ExtensionAuthor> authors = extension.getAuthors();
        if (!authors.isEmpty()) {
            Element authorsElement = document.createElement(ELEMENT_AUTHORS);
            parentElement.appendChild(authorsElement);

            for (ExtensionAuthor author : authors) {
                Element authorElement = document.createElement(ELEMENT_AAUTHOR);
                authorsElement.appendChild(authorElement);

                addElement(document, authorElement, ELEMENT_AANAME, author.getName());

                URL authorURL = author.getURL();
                if (authorURL != null) {
                    addElement(document, authorElement, ELEMENT_AAURL, authorURL.toString());
                }
            }
        }
    }

    private void addScm(Document document, Element extensionElement, Extension extension) {
        ExtensionScm scm = extension.getScm();

        if (scm != null) {
            Element scmElement = document.createElement(ELEMENT_SCM);
            extensionElement.appendChild(scmElement);

            addElement(document, scmElement, ELEMENT_SURL, scm.getUrl());
            addScmConnection(document, scmElement, scm.getConnection(), ELEMENT_SCONNECTION);
            addScmConnection(document, scmElement, scm.getDeveloperConnection(), ELEMENT_SDEVELOPERCONNECTION);
        }
    }

    private void addScmConnection(Document document, Element scmElement, ExtensionScmConnection connection,
            String elementName) {
        if (connection != null) {
            Element connectionElement = document.createElement(elementName);
            scmElement.appendChild(connectionElement);

            addElement(document, connectionElement, ELEMENT_SCSYSTEM, connection.getSystem());
            addElement(document, connectionElement, ELEMENT_SCPATH, connection.getPath());
        }
    }

    private void addIssueManagement(Document document, Element extensionElement, Extension extension) {
        ExtensionIssueManagement issueManagement = extension.getIssueManagement();

        if (issueManagement != null) {
            Element issuemanagementElement = document.createElement(ELEMENT_ISSUEMANAGEMENT);
            extensionElement.appendChild(issuemanagementElement);

            addElement(document, issuemanagementElement, ELEMENT_ISYSTEM, issueManagement.getSystem());
            addElement(document, issuemanagementElement, ELEMENT_IURL, issueManagement.getURL());
        }
    }

    private void addDependencies(Document document, Element parentElement, Extension extension) {
        if (extension.getDependencies() != null && !extension.getDependencies().isEmpty()) {
            Element dependenciesElement = document.createElement(ELEMENT_DEPENDENCIES);
            parentElement.appendChild(dependenciesElement);

            for (ExtensionDependency dependency : extension.getDependencies()) {
                Element dependencyElement = document.createElement(ELEMENT_DDEPENDENCY);
                dependenciesElement.appendChild(dependencyElement);

                addElement(document, dependencyElement, ELEMENT_ID, dependency.getId());
                addElement(document, dependencyElement, ELEMENT_VERSION,
                        dependency.getVersionConstraint().getValue());
                addProperties(document, dependencyElement, dependency.getProperties());
            }
        }
    }

    private void addProperties(Document document, Element parentElement, Map<String, Object> properties) {
        if (!properties.isEmpty()) {
            Element propertiesElement = document.createElement(ELEMENT_PROPERTIES);
            parentElement.appendChild(propertiesElement);

            for (Map.Entry<String, Object> entry : properties.entrySet()) {
                addElement(document, propertiesElement, entry.getKey(), entry.getValue());
            }
        }
    }

    // Tools

    private void addElement(Document document, Element parentElement, String elementName, Object elementValue) {
        Element element = CollectionExtensionPropertySerializer.toElement(elementValue, document, elementName,
                this.serializerByClass);

        if (element != null) {
            parentElement.appendChild(element);
        }
    }
}