/*
 * Copyright 2005-2025 the original author or authors.
 *
 * Licensed 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
 *
 *      https://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 org.springframework.ws.server.endpoint;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import org.springframework.xml.DocumentBuilderFactoryUtils;
import org.springframework.xml.transform.TransformerObjectSupport;

/**
 * Abstract base class for endpoints that handle the message payload as DOM elements.
 * <p>
 * Offers the message payload as a DOM {@code Element}, and allows subclasses to create a
 * response by returning an {@code Element}.
 * <p>
 * An {@code AbstractDomPayloadEndpoint} only accept <em>one</em> payload element.
 * Multiple payload elements are not in accordance with WS-I.
 *
 * @author Arjen Poutsma
 * @author Alef Arendsen
 * @since 1.0.0
 * @see #invokeInternal(org.w3c.dom.Element,org.w3c.dom.Document)
 * @deprecated as of Spring Web Services 2.0, in favor of annotated endpoints
 */
@Deprecated
public abstract class AbstractDomPayloadEndpoint extends TransformerObjectSupport implements PayloadEndpoint {

	private DocumentBuilderFactory documentBuilderFactory;

	private boolean validating = false;

	private boolean namespaceAware = true;

	private boolean expandEntityReferences = false;

	private boolean alwaysTransform = false;

	/**
	 * Set whether or not the XML parser should be XML namespace aware. Default is
	 * {@code true}.
	 */
	public void setNamespaceAware(boolean namespaceAware) {
		this.namespaceAware = namespaceAware;
	}

	/** Set if the XML parser should validate the document. Default is {@code false}. */
	public void setValidating(boolean validating) {
		this.validating = validating;
	}

	/**
	 * Set if the XML parser should expand entity reference nodes. Default is
	 * {@code false}.
	 */
	public void setExpandEntityReferences(boolean expandEntityRef) {
		this.documentBuilderFactory.setExpandEntityReferences(expandEntityRef);
	}

	/**
	 * Set if the request {@link Source} should always be transformed into a new
	 * {@link DOMResult}.
	 * <p>
	 * Default is {@code false}, which is faster.
	 */
	public void setAlwaysTransform(boolean alwaysTransform) {
		this.alwaysTransform = alwaysTransform;
	}

	@Override
	public final Source invoke(Source request) throws Exception {
		if (this.documentBuilderFactory == null) {
			this.documentBuilderFactory = createDocumentBuilderFactory();
		}
		DocumentBuilder documentBuilder = createDocumentBuilder(this.documentBuilderFactory);
		Element requestElement = getDocumentElement(request, documentBuilder);
		Document responseDocument = documentBuilder.newDocument();
		Element responseElement = invokeInternal(requestElement, responseDocument);
		return (responseElement != null) ? new DOMSource(responseElement) : null;
	}

	/**
	 * Create a {@code DocumentBuilder} that this endpoint will use for parsing XML
	 * documents. Can be overridden in subclasses, adding further initialization of the
	 * builder.
	 * @param factory the {@code DocumentBuilderFactory} that the DocumentBuilder should
	 * be created with
	 * @return the {@code DocumentBuilder}
	 * @throws ParserConfigurationException if thrown by JAXP methods
	 */
	protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory)
			throws ParserConfigurationException {
		return factory.newDocumentBuilder();
	}

	/**
	 * Create a {@code DocumentBuilderFactory} that this endpoint will use for
	 * constructing XML documents. Can be overridden in subclasses, adding further
	 * initialization of the factory. The resulting {@code DocumentBuilderFactory} is
	 * cached, so this method will only be called once.
	 * @return the DocumentBuilderFactory
	 * @throws ParserConfigurationException if thrown by JAXP methods
	 */
	protected DocumentBuilderFactory createDocumentBuilderFactory() throws ParserConfigurationException {
		DocumentBuilderFactory factory = DocumentBuilderFactoryUtils.newInstance();
		factory.setValidating(this.validating);
		factory.setNamespaceAware(this.namespaceAware);
		factory.setExpandEntityReferences(this.expandEntityReferences);
		return factory;
	}

	/**
	 * Returns the payload element of the given source.
	 * <p>
	 * Default implementation checks whether the source is a {@link DOMSource}, and
	 * returns the {@linkplain DOMSource#getNode() node} of that. In all other cases, or
	 * when {@linkplain #setAlwaysTransform(boolean) alwaysTransform} is {@code true}, the
	 * source is transformed into a {@link DOMResult}, which is more expensive. If the
	 * passed source is {@code null}, {@code null} is returned.
	 * @param source the source to return the root element of; can be {@code null}
	 * @param documentBuilder the document builder to be used for transformations
	 * @return the document element
	 * @throws TransformerException in case of errors
	 */
	protected Element getDocumentElement(Source source, DocumentBuilder documentBuilder) throws TransformerException {
		if (source == null) {
			return null;
		}
		if (!this.alwaysTransform && source instanceof DOMSource) {
			Node node = ((DOMSource) source).getNode();
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				return (Element) node;
			}
			else if (node.getNodeType() == Node.DOCUMENT_NODE) {
				return ((Document) node).getDocumentElement();
			}
		}
		// we have no other option than to transform
		Document requestDocument = documentBuilder.newDocument();
		DOMResult domResult = new DOMResult(requestDocument);
		transform(source, domResult);
		return requestDocument.getDocumentElement();
	}

	/**
	 * Template method that subclasses must implement to process the request.
	 * <p>
	 * Offers the request payload as a DOM {@code Element}, and allows subclasses to
	 * return a response {@code Element}.
	 * <p>
	 * The given DOM {@code Document} is to be used for constructing {@code Node}s, by
	 * using the various {@code create} methods.
	 * @param requestElement the contents of the SOAP message as DOM elements
	 * @param responseDocument a DOM document to be used for constructing {@code Node}s
	 * @return the response element. Can be {@code null} to specify no response.
	 */
	protected abstract Element invokeInternal(Element requestElement, Document responseDocument) throws Exception;

}
