All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.joekerouac.common.tools.codec.xml.Dom4JXmlCodec Maven / Gradle / Ivy

The newest version!
// Generated by delombok at Fri Mar 14 11:41:38 CST 2025
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
 * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
 * to You under the Apache 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.apache.org/licenses/LICENSE-2.0
 *
 * 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 com.github.joekerouac.common.tools.codec.xml;

import com.github.joekerouac.common.tools.codec.Codec;
import com.github.joekerouac.common.tools.codec.exception.SerializeException;
import com.github.joekerouac.common.tools.codec.xml.deserializer.BeanDeserializer;
import com.github.joekerouac.common.tools.codec.xml.deserializer.Deserializers;
import com.github.joekerouac.common.tools.enums.ErrorCodeEnum;
import com.github.joekerouac.common.tools.exception.CommonException;
import com.github.joekerouac.common.tools.io.IOUtils;
import com.github.joekerouac.common.tools.reflect.AccessorUtil;
import com.github.joekerouac.common.tools.reflect.ClassUtils;
import com.github.joekerouac.common.tools.reflect.bean.BeanUtils;
import com.github.joekerouac.common.tools.reflect.bean.PropertyEditor;
import com.github.joekerouac.common.tools.reflect.type.AbstractTypeReference;
import com.github.joekerouac.common.tools.reflect.type.JavaType;
import com.github.joekerouac.common.tools.reflect.type.JavaTypeUtil;
import com.github.joekerouac.common.tools.string.StringUtils;
import com.github.joekerouac.common.tools.util.Assert;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

/**
 * dom4j实现的xml解析器,目前支持类的泛型解析,但是不支持Map、List的泛型解析;
 *
 *
 * 注意:非线程安全,同时可能会有内存问题,大xml请使用流式解析;
 *
 * @since 1.0.0
 * @author JoeKerouac
 * @date 2022-10-14 14:37:00
 */
public class Dom4JXmlCodec implements Codec {
    @java.lang.SuppressWarnings("all")
    @lombok.Generated
    private static final com.github.joekerouac.common.tools.log.Logger LOGGER = com.github.joekerouac.common.tools.log.LoggerFactory.getLogger(Dom4JXmlCodec.class.getName());
    private static final String DEFAULT_ROOT = "root";
    protected final SAXReader reader;
    private final Map> deserializers;
    private final boolean writeHeader;

    public Dom4JXmlCodec() {
        this(false);
    }

    public Dom4JXmlCodec(boolean writeHeader) {
        this.writeHeader = writeHeader;
        reader = new SAXReader();
        deserializers = new ConcurrentHashMap<>();
        deserializers.putAll(Deserializers.defaultDeserializers);
        enableDTD(false);
    }

    @SuppressWarnings("unchecked")
    public  XmlDeserializer addDeserializer(Class type, XmlDeserializer deserializer) {
        return (XmlDeserializer) deserializers.put(JavaTypeUtil.createJavaType(type), deserializer);
    }

    @SuppressWarnings("unchecked")
    public  XmlDeserializer addDeserializer(AbstractTypeReference reference, XmlDeserializer deserializer) {
        return (XmlDeserializer) deserializers.put(JavaTypeUtil.createJavaType(reference), deserializer);
    }

    @SuppressWarnings("unchecked")
    public  XmlDeserializer addDeserializer(JavaType javaType, XmlDeserializer deserializer) {
        return (XmlDeserializer) deserializers.put(javaType, deserializer);
    }

    /**
     * 设置DTD支持
     *
     * @param enable
     *            true表示支持DTD,false表示不支持
     */
    public void enableDTD(boolean enable) {
        // 允许DTD会有XXE漏洞,关于XXE漏洞:https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet
        if (enable) {
            // 打开DTD支持,高危操作,除非你清楚你在做什么,否则不要打开
            setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);
            setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", true);
            setFeature("http://xml.org/sax/features/external-general-entities", true);
            setFeature("http://xml.org/sax/features/external-parameter-entities", true);
        } else {
            // 不允许DTD
            setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            setFeature("http://xml.org/sax/features/external-general-entities", false);
            setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        }
    }

    /**
     * 配置SAXReader
     *
     * @param k
     *            name
     * @param enable
     *            是否允许,true表示允许
     */
    public void setFeature(String k, boolean enable) {
        try {
            reader.setFeature(k, enable);
        } catch (SAXException e) {
            throw new RuntimeException("设置属性失败:[" + k + ":" + enable + "]");
        }
    }

    /**
     * 将xml解析为Document
     *
     * @param text
     *            xml文本
     * @return Document
     * @throws DocumentException
     *             DocumentException
     */
    private Document parseText(String text) throws DocumentException {
        Document result;
        String encoding = getEncoding(text);
        InputSource source = new InputSource(new StringReader(text));
        source.setEncoding(encoding);
        result = reader.read(source);
        // if the XML parser doesn't provide a way to retrieve the encoding,
        // specify it manually
        if (result.getXMLEncoding() == null) {
            result.setXMLEncoding(encoding);
        }
        return result;
    }

    /**
     * 获取xml的编码方式
     *
     * @param text
     *            xml文件
     * @return xml的编码
     */
    private String getEncoding(String text) {
        String result = null;
        String xml = text.trim();
        if (xml.startsWith("");
            String sub = xml.substring(5, end).trim();
            StringTokenizer tokens = new StringTokenizer(sub, " =\"\'");
            while (tokens.hasMoreTokens()) {
                String token = tokens.nextToken();
                if ("encoding".equals(token)) {
                    if (tokens.hasMoreTokens()) {
                        result = tokens.nextToken();
                    }
                    break;
                }
            }
        }
        return result;
    }

    /**
     * 解析XML,将xml解析为map(注意:如果XML是<a>data<b>bbb</b></a> 这种格式那么data将不被解析,对于list可以正确解析)
     *
     * @param xml
     *            xml字符串
     * @param mapClass
     *            map的class
     * @return 由xml解析的Map,可能是String类型或者Map<String, Object>类型,其中Map的value有可能 是Stirng类型,也有可能是List<String>类型
     */
    @SuppressWarnings("unchecked")
    public Map parseToMap(String xml, Class mapClass) {
        Assert.argNotNull(mapClass, "mapClass");
        Assert.argNotBlank(xml, "xml");
        Map map = newMap(mapClass);
        try {
            Document document = parseText(xml);
            Element root = document.getRootElement();
            if (root.elements().size() == 0) {
                map.put(root.getName(), root.getText());
            } else {
                map.putAll((Map) parse(root));
            }
            return map;
        } catch (Exception e) {
            LOGGER.error(e, "xml格式不正确");
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private  Map newMap(Class mapClass) {
        if (!Map.class.isAssignableFrom(mapClass)) {
            throw new SerializeException(ErrorCodeEnum.CODE_ERROR, "请传入正确的Map的class");
        }
        Class realMapClass = mapClass;
        if (Map.class.equals(mapClass)) {
            realMapClass = HashMap.class;
        }
        if (AccessorUtil.isAbstract(realMapClass)) {
            throw new SerializeException(ErrorCodeEnum.CODE_ERROR, String.format("不支持的map类型[%s],请传入实际的map类型", mapClass));
        }
        return (Map) ClassUtils.getInstance(realMapClass);
    }

    /**
     * 将XML解析为POJO对象,暂时无法解析map,当需要解析的字段是{@link java.util.Collection}的子类时必须带有注 解{@link XmlNode},否则将解析失败。
     * 

* PS:对象中的集合字段必须添加注解,必须是简单集合,即集合中只有一种数据类型,并且当类型不是String时需要指定Deserializer,否则将会解析失败。 * * @param xml * XML源 * @param reference * POJO对象的类型 * @param * POJO的实际类型 * @return 解析结果 */ @SuppressWarnings("unchecked") private T parse(String xml, AbstractTypeReference reference) { Assert.argNotBlank(xml, "xml"); Assert.argNotNull(reference, "clazz"); if (StringUtils.isBlank(xml)) { return null; } JavaType javaType = JavaTypeUtil.createJavaType(reference); Class rawClass = javaType.getRawClass(); if (Map.class.isAssignableFrom(rawClass)) { return (T) parseToMap(xml, rawClass); } else if (rawClass.getName().startsWith("java.")) { // 对java内置对象不支持,其实该解析器仅支持自定义pojo类,其他对象都不支持,不过java内置对象排除成本最低,所以先排除 throw new CommonException(ErrorCodeEnum.CODE_ERROR, StringUtils.format("不支持的类型:[{}]", rawClass)); } Document document; // 解析XML try { document = parseText(xml); } catch (Exception e) { LOGGER.error(e, "xml解析错误"); return null; } Element root = document.getRootElement(); if (root == null) { return null; } XmlDeserializer xmlDeserializer = (XmlDeserializer) deserializers.compute(javaType, (key, deserializer) -> { if (deserializer == null) { deserializer = new BeanDeserializer<>(key, deserializers); } return deserializer; }); return xmlDeserializer.read(root, null); } /** * 解析element * * @param element * element * @return 解析结果,可能是String类型或者Map<String, Object>类型,其中Map的value有可能是Stirng类型,也有可能 是List<String>类型 */ @SuppressWarnings("unchecked") private Object parse(Element element) { List elements = element.elements(); if (elements.size() == 0) { return element.getText(); } else { Map map = new HashMap<>(); for (Element ele : elements) { Object result = parse(ele); if (map.containsKey(ele.getName())) { // 如果map中已经包含该key,说明该key有多个,是个list Object obj = map.get(ele.getName()); List list; if (obj instanceof List) { // 如果obj不等于null并且是list对象,说明map中存的已经是一个list list = (List) obj; } else { // 如果obj等于null或者不是list对象,那么新建一个list对象 list = new ArrayList<>(); list.add(obj == null ? null : String.valueOf(obj)); } list.add(result == null ? null : String.valueOf(result)); map.put(ele.getName(), list); } else { map.put(ele.getName(), result); } } return map; } } /** * 将Object解析为xml,根节点为root,字段值为null的将不包含在xml中,暂时只能解析基本类型(可以正确解析list、map) * * @param source * bean * @param charset * 字符集 * @return 解析结果 */ public String toXml(Object source, Charset charset) { return toXml(source, charset, null, false); } /** * 将Object解析为xml,暂时只能解析基本类型(可以正确解析list、map) * * @param source * bean * @param charset * 字符集 * @param rootName * 根节点名称,如果为null则会尝试使用默认值 * @param hasNull * 是否包含null元素(true:包含) * @return 解析结果 */ public String toXml(Object source, Charset charset, String rootName, boolean hasNull) { return toXml(source, charset, rootName, hasNull, false); } /** * 将Object解析为xml,暂时只能解析基本类型(可以正确解析list、map) * * @param source * bean * @param charset * 字符集 * @param defaultRootName * 根节点名称,如果为null则会尝试使用默认值 * @param hasNull * 是否包含null元素(true:包含) * @param pretty * 是否美化输出,true表示美化输出 * @return 解析结果 */ public String toXml(Object source, Charset charset, String defaultRootName, boolean hasNull, boolean pretty) { Element root = toXmlElement(source, defaultRootName, hasNull); OutputFormat format = pretty ? OutputFormat.createPrettyPrint() : new OutputFormat(); format.setEncoding(charset.name()); StringWriter out = new StringWriter(); try { XMLWriter writer = new XMLWriter(out, format); if (writeHeader) { Document document = DocumentHelper.createDocument(root); writer.write(document); } else { writer.write(root); } writer.flush(); return out.toString(); } catch (Exception e) { throw new SerializeException(ErrorCodeEnum.SERIAL_EXCEPTION, "序列化异常", e); } } /** * 将pojo构建为文档 * * @param source * bean * @param defaultRootName * 根节点名称,如果为null则会尝试使用默认值 * @param hasNull * 是否包含null元素(true:包含) * @return 构建的文档节点 */ public Element toXmlElement(Object source, String defaultRootName, boolean hasNull) { if (source == null) { LOGGER.warn("传入的source为null,返回null"); return null; } XmlNode xmlNode = getXmlNodeFromClass(source.getClass()); String rootNameStr = defaultRootName; if (StringUtils.isBlank(rootNameStr) && xmlNode != null) { rootNameStr = xmlNode.name(); } if (StringUtils.isBlank(rootNameStr)) { rootNameStr = DEFAULT_ROOT; } QName rootName; if (xmlNode != null && StringUtils.isNotBlank(xmlNode.namespace())) { rootName = DocumentHelper.createQName(rootNameStr, DocumentHelper.createNamespace(xmlNode.nsPrefix(), xmlNode.namespace())); } else { rootName = DocumentHelper.createQName(rootNameStr); } Long start = System.currentTimeMillis(); Element root = DocumentHelper.createElement(rootName); buildDocument(root, source, source.getClass(), !hasNull); Long end = System.currentTimeMillis(); LOGGER.debug("解析xml用时" + (end - start) + "ms"); return root; } /** * 根据pojo构建xml的document(方法附件参考附件xml解析器思路) * * @param parent * 父节点 * @param pojo * pojo,不能为空 * @param clazz * pojo的Class * @param ignoreNull * 是否忽略空元素 */ @SuppressWarnings("unchecked") private void buildDocument(Element parent, Object pojo, Class clazz, boolean ignoreNull) { // 字段描述,key是节点名,value是XmlData Map map = new LinkedHashMap<>(); // 构建字段描述 if (pojo instanceof Map) { Map pojoMap = (Map) pojo; pojoMap.forEach((k, v) -> { if (k == null) { LOGGER.debug("忽略map中key为null的值"); } else { if (ignoreNull && v == null) { LOGGER.debug("当前配置为忽略空值,[{}]的值为空,忽略", k); } else { map.put(String.valueOf(k), new XmlData(null, v, v == null ? null : v.getClass())); } } }); } else { PropertyEditor[] propertyEditors = BeanUtils.getPropertyDescriptors(clazz == null ? pojo.getClass() : clazz); for (PropertyEditor editor : propertyEditors) { XmlNode xmlNode = editor.getAnnotation(XmlNode.class); // 如果没有读取方法,并且没有加注解,忽略该字段 if (xmlNode == null && !editor.hasReadMethod()) { LOGGER.debug("字段[{}]没有XmlNode注解并且没有读取方法,忽略该字段", editor.name()); continue; } if (AccessorUtil.isTransient(editor.original())) { LOGGER.debug("字段[{}]是transient修饰的,不进行写出序列化", editor.name()); continue; } // 字段值 try { Object valueObj = pojo == null ? null : BeanUtils.getProperty(pojo, editor.name()); // 判断是否忽略 if ((ignoreNull && valueObj == null) || (xmlNode != null && xmlNode.ignore())) { LOGGER.debug("忽略空节点或者节点被注解忽略"); continue; } // 节点名 String nodeName = (xmlNode == null || StringUtils.isBlank(xmlNode.name())) ? editor.name() : xmlNode.name(); map.put(nodeName, new XmlData(xmlNode, valueObj, editor.type())); } catch (Exception e) { LOGGER.error(e, "获取字段值时发生异常,忽略改值"); } } } map.forEach((k, v) -> { XmlNode xmlNode = v.getXmlNode(); Object valueObj = v.getData(); // 节点名 // 属性名 String attrName = (xmlNode == null || StringUtils.isBlank(xmlNode.attributeName())) ? k : xmlNode.attributeName(); // 是否是cdata boolean isCDATA = xmlNode != null && xmlNode.isCDATA(); // 数据类型 Class type = v.getType(); // 构建一个对应的节点 Element node = parent.element(k); if (node == null) { // 搜索不到,创建一个(在属性是父节点属性的情况和节点是list的情况需要将该节点删除) Namespace namespace = parent.getNamespace(); QName name; if (xmlNode != null && StringUtils.isNotBlank(xmlNode.namespace())) { namespace = DocumentHelper.createNamespace(xmlNode.nsPrefix(), xmlNode.namespace()); } if (namespace == null) { name = DocumentHelper.createQName(k); } else { name = DocumentHelper.createQName(k, namespace); } node = DocumentHelper.createElement(name); parent.add(node); } // 判断字段对应的是否是属性 if (xmlNode != null && (xmlNode.isAttribute())) { // 判断是否是父节点的属性 if (StringUtils.isBlank(xmlNode.name())) { // 如果是父节点那么删除之前添加的 parent.remove(node); node = parent; } Element finalNode = node; if (Map.class.isAssignableFrom(type)) { // 这里要判断valueObj是否等于null,因为如果不忽略null的话这里是有可能传过来一个null值的 if (valueObj != null) { Map attrs = (Map) valueObj; attrs.forEach(finalNode::addAttribute); } } else { // 属性值,属性值只能是简单值 String attrValue = valueObj == null ? "" : String.valueOf(valueObj); finalNode.addAttribute(attrName, attrValue); } } else if (type == null) { LOGGER.debug("当前不知道节点[{}]的类型,忽略该节点", k); } else if (JavaTypeUtil.isNotPojo(type)) { // 是简单类型或者集合类型 if (Map.class.isAssignableFrom(type)) { LOGGER.warn("当前字段[{}]是map类型", k); buildDocument(node, v.getData(), type, ignoreNull); } else if (Collection.class.isAssignableFrom(type)) { parent.remove(node); // 集合类型 // 判断字段值是否为null if (valueObj != null) { String arrayNodeName; Element root; if (xmlNode == null || StringUtils.isBlank(xmlNode.arrayRoot())) { arrayNodeName = k; root = parent; } else { arrayNodeName = xmlNode.arrayRoot(); root = DocumentHelper.createElement(k); parent.add(root); } Collection collection = (Collection) valueObj; collection.forEach(obj -> { Element n = DocumentHelper.createElement(arrayNodeName); root.add(n); buildDocument(n, obj, null, ignoreNull); }); } } else { String text = valueObj == null ? "" : String.valueOf(valueObj); if (isCDATA) { LOGGER.debug("内容[{}]需要CDATA标签包裹", text); node.add(DocumentHelper.createCDATA(text)); } else { node.setText(text); } } } else { // 猜测字段类型(防止字段的声明是一个接口,优先采用xmlnode中申明的类型) Class realType = resolveRealType(type, xmlNode); // pojo类型 buildDocument(node, valueObj, realType, ignoreNull); } }); } /** * 确定字段的真实类型 * * @param fieldType * 字段类型 * @param xmlNode * 字段XmlNode注解 * @return 字段实际类型而不是接口或者抽象类 */ private Class resolveRealType(Class fieldType, XmlNode xmlNode) { // 猜测字段类型(防止字段的声明是一个接口,优先采用xmlnode中申明的类型) Class type = (xmlNode == null) ? fieldType : xmlNode.general(); if (!fieldType.isAssignableFrom(type)) { type = fieldType; } return type; } /** * 从类上获取XmlNode注解,如果不存在则尝试从父类上获取 * * @param clazz * class * @return XmlNode */ private XmlNode getXmlNodeFromClass(Class clazz) { XmlNode xmlNode = clazz.getDeclaredAnnotation(XmlNode.class); if (xmlNode != null) { return xmlNode; } if (clazz.getSuperclass().equals(Object.class)) { return null; } return getXmlNodeFromClass(clazz.getSuperclass()); } @Override public T read(InputStream inputStream, Charset charset, AbstractTypeReference typeReference) throws SerializeException { byte[] data = IOUtils.read(inputStream, true); return parse(new String(data, charset), typeReference); } @Override public byte[] write(Object data, Charset charset) { return toXml(data, charset).getBytes(charset == null ? StandardCharsets.UTF_8 : charset); } @Override public void write(Object data, Charset resultCharset, OutputStream outputStream) { Charset charset = resultCharset == null ? StandardCharsets.UTF_8 : resultCharset; byte[] xmlData = toXml(data, charset).getBytes(charset); try { outputStream.write(xmlData); outputStream.flush(); } catch (IOException e) { throw new SerializeException(ErrorCodeEnum.SERIAL_EXCEPTION, e); } } /** * XML节点数据 */ private static class XmlData { /** * 节点注解,可以为空 */ private XmlNode xmlNode; /** * 节点数据 */ private Object data; /** * 节点数据的实际类型,可以为空 */ private Class type; @java.lang.SuppressWarnings("all") @lombok.Generated public XmlNode getXmlNode() { return this.xmlNode; } @java.lang.SuppressWarnings("all") @lombok.Generated public Object getData() { return this.data; } @java.lang.SuppressWarnings("all") @lombok.Generated public Class getType() { return this.type; } @java.lang.SuppressWarnings("all") @lombok.Generated public void setXmlNode(final XmlNode xmlNode) { this.xmlNode = xmlNode; } @java.lang.SuppressWarnings("all") @lombok.Generated public void setData(final Object data) { this.data = data; } @java.lang.SuppressWarnings("all") @lombok.Generated public void setType(final Class type) { this.type = type; } @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated public boolean equals(final java.lang.Object o) { if (o == this) return true; if (!(o instanceof Dom4JXmlCodec.XmlData)) return false; final Dom4JXmlCodec.XmlData other = (Dom4JXmlCodec.XmlData) o; if (!other.canEqual((java.lang.Object) this)) return false; final java.lang.Object this$xmlNode = this.getXmlNode(); final java.lang.Object other$xmlNode = other.getXmlNode(); if (this$xmlNode == null ? other$xmlNode != null : !this$xmlNode.equals(other$xmlNode)) return false; final java.lang.Object this$data = this.getData(); final java.lang.Object other$data = other.getData(); if (this$data == null ? other$data != null : !this$data.equals(other$data)) return false; final java.lang.Object this$type = this.getType(); final java.lang.Object other$type = other.getType(); if (this$type == null ? other$type != null : !this$type.equals(other$type)) return false; return true; } @java.lang.SuppressWarnings("all") @lombok.Generated protected boolean canEqual(final java.lang.Object other) { return other instanceof Dom4JXmlCodec.XmlData; } @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated public int hashCode() { final int PRIME = 59; int result = 1; final java.lang.Object $xmlNode = this.getXmlNode(); result = result * PRIME + ($xmlNode == null ? 43 : $xmlNode.hashCode()); final java.lang.Object $data = this.getData(); result = result * PRIME + ($data == null ? 43 : $data.hashCode()); final java.lang.Object $type = this.getType(); result = result * PRIME + ($type == null ? 43 : $type.hashCode()); return result; } @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated public java.lang.String toString() { return "Dom4JXmlCodec.XmlData(xmlNode=" + this.getXmlNode() + ", data=" + this.getData() + ", type=" + this.getType() + ")"; } @java.lang.SuppressWarnings("all") @lombok.Generated public XmlData(final XmlNode xmlNode, final Object data, final Class type) { this.xmlNode = xmlNode; this.data = data; this.type = type; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy