package org.xbib.io.webdav.common;

import org.xml.sax.EntityResolver;
import org.xml.sax.helpers.DefaultHandler;
import java.io.IOException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * Custom {@link DocumentBuilderFactory} extended for use in WebDAV.
 */
public class DavDocumentBuilderFactory {

    private final DocumentBuilderFactory DEFAULT_FACTORY = createFactory();

    private DocumentBuilderFactory BUILDER_FACTORY = DEFAULT_FACTORY;

    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilder db = BUILDER_FACTORY.newDocumentBuilder();
        if (BUILDER_FACTORY == DEFAULT_FACTORY) {
            // if this is the default factory: set the default entity resolver as well
            db.setEntityResolver(DEFAULT_ENTITY_RESOLVER);
        }
        db.setErrorHandler(new DefaultHandler());
        return db;
    }

    /**
     * Support the replacement of {@link #BUILDER_FACTORY}. This is useful
     * for injecting a customized BuilderFactory, for example with one that
     * uses a local catalog resolver. This is one technique for addressing
     * this issue:
     * http://www.w3.org/blog/systeam/2008/02/08/w3c_s_excessive_dtd_traffic
     *
     * @param documentBuilderFactory
     */
    public void setFactory(DocumentBuilderFactory documentBuilderFactory) {
        //LOG.log(Level.FINE, "DocumentBuilderFactory changed to: " + documentBuilderFactory);
        BUILDER_FACTORY = documentBuilderFactory != null ? documentBuilderFactory : DEFAULT_FACTORY;
    }

    private DocumentBuilderFactory createFactory() {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        factory.setIgnoringComments(true);
        factory.setIgnoringElementContentWhitespace(true);
        factory.setCoalescing(true);
        try {
            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        } catch (ParserConfigurationException | AbstractMethodError e) {
            //LOG.log(Level.WARNING, "Secure XML processing is not supported", e);
        }
        return factory;
    }

    /**
     * An entity resolver that does not allow external entity resolution. See
     * RFC 4918, Section 20.6
     */
    private static final EntityResolver DEFAULT_ENTITY_RESOLVER = (publicId, systemId) -> {
        //LOG.log(Level.FINE, "Resolution of external entities in XML payload not supported - publicId: " + publicId + ", systemId: "
        //       + systemId);
        throw new IOException("This parser does not support resolution of external entities (publicId: " + publicId
                + ", systemId: " + systemId + ")");
    };
}
