/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.common.xml.jaxb;

import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.sessions.SessionEventListener;
import org.xml.sax.InputSource;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * <pre>https://blog.csdn.net/jiangchao858/article/details/82818240</pre>
 *
 * @author 田平 create 2020年2月14日下午3:25:01
 */
public class JaxbUtil {

	private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();

	private static final Map<String, Object> DEFAULT_PROPS = new HashMap<String, Object>();

	static {
		SessionEventListener listener = new NullPolicyEventListener();
		DEFAULT_PROPS.put(JAXBContextProperties.SESSION_EVENT_LISTENER, listener);
	}

	public static JAXBContext getCxt(Class<?>... classesToBeBound) {
		return getCxt(classesToBeBound, DEFAULT_PROPS);
	}

	public static JAXBContext getCxt(Class<?>[] classesToBeBound, Map<String, ?> props) {
		JAXBContext context = null;
		try {
			Map<String, Object> realProps = new HashMap<String, Object>();
			realProps.putAll(DEFAULT_PROPS);
			if (props != null) {
				realProps.putAll(props);
			}
			context = JAXBContextFactory.createContext(classesToBeBound, props);
		} catch (JAXBException e) {
			throw new RuntimeException("getCxt", e);
		}
		return context;
	}

	public static JAXBContext getCxt(String contextPath) {
		return getCxt(contextPath, null);
	}

	public static JAXBContext getCxt(String contextPath, ClassLoader classLoader) {

		return getCxt(contextPath, classLoader, DEFAULT_PROPS);
	}

	public static JAXBContext getCxt(String contextPath, ClassLoader classLoader, Map<String, ?> props) {
		JAXBContext context = null;
		try {
			Map<String, Object> realProps = new HashMap<String, Object>();
			realProps.putAll(DEFAULT_PROPS);
			if (props != null) {
				realProps.putAll(props);
			}
			context = JAXBContextFactory.createContext(contextPath, classLoader, props);
		} catch (JAXBException e) {
			throw new RuntimeException("getCxt", e);
		}
		return context;
	}

	public static Marshaller getMarshaller(JAXBContext ctx) {

		return getMarshaller(ctx, DEFAULT_ENCODING);

	}

	public static Marshaller getMarshaller(Class<?>... classesToBeBound) {

		return getMarshaller(getCxt(classesToBeBound), DEFAULT_ENCODING);

	}

	public static Marshaller getMarshaller(String encoding, Class<?>... classesToBeBound) {

		return getMarshaller(getCxt(classesToBeBound), encoding);

	}

	public static Marshaller getMarshaller(JAXBContext ctx, String encoding) {
		try {
			Marshaller marshaller = ctx.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
			marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
			marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);
			return marshaller;
		} catch (JAXBException e) {
			throw new RuntimeException("getMarshaller", e);
		}
	}

	public static Unmarshaller getUnmarshaller(Class<?>... classesToBeBound) {
		return getUnmarshaller(getCxt(classesToBeBound));
	}

	public static Unmarshaller getUnmarshaller(JAXBContext ctx) {
		try {
			Unmarshaller unmarshaller = ctx.createUnmarshaller();
			unmarshaller.setAdapter(new JaxbDateSerializer());
			return unmarshaller;
		} catch (JAXBException e) {
			throw new RuntimeException("getUnmarshaller", e);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> T xml2Bean(String xml, Class<T> classesToBeBound) {
		try {
			Unmarshaller unmarshaller = getUnmarshaller(JAXBContext.newInstance(classesToBeBound));
			/**
			 * XXE 漏洞
			 * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXB_Unmarshaller
			 */
			SAXParserFactory spf = SAXParserFactory.newInstance();
			spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
			spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
			spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

			Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(new StringReader(xml)));
			return (T) unmarshaller.unmarshal(xmlSource);
		} catch (Exception e) {
			throw new RuntimeException("xml2Bean", e);
		}
	}

	public static String bean2Xml(Object obj) {
		Marshaller marshaller = getMarshaller(obj.getClass());
		StringWriter writer = new StringWriter(512);
		try {
			marshaller.marshal(obj, writer);
		} catch (JAXBException e) {
			throw new RuntimeException("bean2Xml", e);
		}
		return writer.toString();
	}

}
