天天看點

常用工具類之十二 Jaxb轉換xml工具類,生産優化版

相對之前寫的進行簡化和優化 Jaxb工具類

生産版,使用JDK自帶xjc指令從xsd生成的java類,包含了xsd驗證,安全轉換xml.

xjc Xx.xsd -p com.taylor.jaxb
 com.taylor.jaxb為生成的java類的包名
 或者
java -Dfile.encoding=UTF-8 -cp "C:\Program Files\Java\jdk1.8.0_191\lib\tools.jar" com.sun.tools.internal.xjc.Driver  -p com.taylor.jaxb  1-Xx.xsd  -npa -no-header -nv

           

需要增加依賴

<dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
           
package com.taylor.xml.customs.datahub;

import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.validation.SchemaFactory;


import com.sun.xml.bind.marshaller.NamespacePrefixMapper;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.jaxen.JaxenException;
import org.jaxen.XPath;
import org.jaxen.dom4j.Dom4jXPath;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.xml.sax.SAXParseException;

@Slf4j
@SuppressWarnings({"unchecked"})
public class JaxbUtil {
	
    static Map<String, JAXBContext> jaxbContextMap = new HashMap<>();

    public static String convert(Object source) {
    	
    	String convert = null;
    	ByteArrayOutputStream os;
        try {
            Class<?> aClass = source.getClass();
            JAXBContext jaxbContext = getJaxbContext(aClass);

            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,  false);
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);

            marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
                @Override
                public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
                    String ds = "http://www.w3.org/2000/09/xmldsig#";
                    String eCommerce = "http://ebxml.customs.go.th/XMLSchema/EECResponseMsg_2_00";
                    if (ds.equals(namespaceUri)) {
                    	return "ds";
                    } else if (eCommerce.equals(namespaceUri)) {
                        return "eCommerce";
                    }
                    return suggestion;
                }
            });
            
            os = new ByteArrayOutputStream();
            marshaller.marshal(source, os);
            
            StringWriter writer = new StringWriter();
            marshaller.marshal(source, writer);
            convert = writer.toString();
            convert = convert.replaceAll(">\\s*<", "><");
        } catch (JAXBException e) {
			log.error("convert Exception: ",e);
		}
        return convert;
    }

    private static JAXBContext getJaxbContext(Class<?> aClass) throws JAXBException {
        JAXBContext jaxbContext = jaxbContextMap.get(aClass.getName());
        if (jaxbContext == null) {
            // 如果每次都調用JAXBContext.newInstance方法,會導緻性能急劇下降
            jaxbContext = JAXBContext.newInstance(aClass);
            jaxbContextMap.put(aClass.getName(), jaxbContext);
        }
        return jaxbContext;
    }

    public static <T> T unconvert(String content, Class<T> clazz) {
        try {
            Unmarshaller unmarshaller = getUnmarshaller(clazz);
            Object obj = unmarshaller.unmarshal(new StringReader(content));
            return (T) obj;
        } catch (JAXBException e) {
			log.error("unconvert JAXBException: ",e);
		} 
        return null;
    }

    public static Exception handler(String info, Class<?> clazz, Exception e) {
        StringBuffer sb = new StringBuffer();
        if (StringUtils.isNotBlank(info)) {
            if (e.getCause() instanceof SAXParseException ) {
                sb.append("封包驗證失敗, ");
            }
            String type = "解析失敗";
            String number = "解析失敗";
            try {
                Document document = DocumentHelper.parseText(info);
                Element element = document.getRootElement();
                type = element.getName();
                number = getElementVal(document,"//ns:DocumentControl/ns:ReferenceNumber",
                        clazz.getAnnotation(XmlRootElement.class).namespace());
            } catch (DocumentException | JaxenException var8) {
                log.error("unconvert Exception: ",e);
            }

            sb.append("封包[類型:").append(type).append(" - ReferenceNumber:").append(number).append("]無法轉換為:").append(clazz.getSimpleName()).append("執行個體, 原因:\n").append((
                    e.getCause() instanceof SAXParseException?e.getCause():e).getMessage());
        } else {
            sb.append(e.toString());
        }

        return new Exception(sb.toString());
    }
    /**
     * 擷取封包指定節點的值
     * 使用xpath進行讀取
     * @param xpathString xpath表達式
     * @param namespace 預設命名空間
     * @return 标簽值 當擷取不到指定的标簽時傳回NULL
     */
    @Nullable
    public static String getElementVal(Document document, String xpathString, String namespace) throws JaxenException {
///        Node node = document.selectSingleNode(xpath);
        XPath xpath = new Dom4jXPath(xpathString);
        xpath.addNamespace("ns", namespace);
        //對于預設命名空間,我們在解析時要自己添加一個字首
        Element ele = (Element)xpath.selectSingleNode(document);
        if (Objects.nonNull(ele) && StringUtils.isNotEmpty(ele.getText())) {
            return ele.getText();
        }
        return null;
    }
    /**
     * 将XML轉為指定的XX封包POJO 并做xsd驗證
     * @param clazz XX封包類型
     * @param xmlStr xml字元串
     * @param <T> 需要轉換的POJO類
     * @return POJO實體 (實際一定能轉換,是以忽略了轉換警告)
     * @throws JAXBException XML格式異常
     * @throws IOException IO異常
     */
    @NonNull
    public static <T> T xmlStrToXx(@NonNull String xmlStr,@NonNull Class<T> clazz) throws Exception {
        try {
            try (Reader reader = new StringReader(xmlStr)){
                Unmarshaller unmarshaller = getUnmarshaller(clazz);
                URL schema = clazz.getResource(EECDCLType.getXsdByClass(clazz));
                Assert.notNull(schema,"xsd不能為空");
                unmarshaller.setSchema(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(schema));
                return clazz.cast(unmarshaller.unmarshal(reader));
            }
        } catch (Exception e) {
            throw handler(xmlStr,clazz,e);
        }
    }

    private static <T> Unmarshaller getUnmarshaller(@NonNull Class<T> clazz) throws JAXBException {
        return getJaxbContext(clazz).createUnmarshaller();
    }

    public static void main(String[] args) {
        try {
            EECCancelDeclarationType type = xmlStrToXx("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                    "<EECCancelDeclaration xmlns=\"http://ebxml.customs.go.th/XMLSchema/EECCancelDeclaration_2_00\">\n" +
                    "    <DocumentControl>\n" +
                    "        <ReferenceNumber>AYUG300000006</ReferenceNumber>\n" +
                    "        <DocumentType>I</DocumentType>\n" +
                    "        <DocumentNumber>A015I620700003</DocumentNumber>\n" +
                    "        <CompanyInfo>\n" +
                    "            <TaxNumber>0107557000101</TaxNumber>\n" +
                    "            <Branch>0000</Branch>\n" +
                    "        </CompanyInfo>\n" +
                    "        <CancelReason>For Test Cancel Type I</CancelReason>\n" +
                    "        <RegistrationID>TH0101075570001010000000009T3</RegistrationID>\n" +
                    "    </DocumentControl>\n" +
                    "</EECCancelDeclaration>", EECCancelDeclarationType.class);
            System.out.println(type);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
}