/*
 * 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.soap.server.endpoint.interceptor;

import java.util.Locale;

import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;

import org.xml.sax.SAXParseException;

import org.springframework.ws.context.MessageContext;
import org.springframework.ws.server.endpoint.interceptor.AbstractValidatingInterceptor;
import org.springframework.ws.soap.SoapBody;
import org.springframework.ws.soap.SoapFault;
import org.springframework.ws.soap.SoapFaultDetail;
import org.springframework.ws.soap.SoapFaultDetailElement;
import org.springframework.ws.soap.SoapMessage;

/**
 * Subclass of {@code AbstractValidatingInterceptor} that creates a SOAP Fault whenever
 * the request message cannot be validated. The contents of the SOAP Fault can be
 * specified by setting the {@code addValidationErrorDetail}, {@code faultStringOrReason},
 * or {@code detailElementName} properties. Further customizing can be accomplished by
 * overriding {@code handleRequestValidationErrors}.
 *
 * @author Arjen Poutsma
 * @since 1.0.0
 * @see #setAddValidationErrorDetail(boolean)
 * @see #setFaultStringOrReason(String)
 * @see #DEFAULT_FAULTSTRING_OR_REASON
 * @see #setDetailElementName(javax.xml.namespace.QName)
 * @see #DEFAULT_DETAIL_ELEMENT_NAME
 * @see #handleResponseValidationErrors(org.springframework.ws.context.MessageContext,org.xml.sax.SAXParseException[])
 */
public abstract class AbstractFaultCreatingValidatingInterceptor extends AbstractValidatingInterceptor {

	/**
	 * Default SOAP Fault Detail name used when a validation errors occur on the request.
	 * @see #setDetailElementName(javax.xml.namespace.QName)
	 */
	public static final QName DEFAULT_DETAIL_ELEMENT_NAME = new QName("http://springframework.org/spring-ws",
			"ValidationError", "spring-ws");

	/**
	 * Default SOAP Fault string used when a validation errors occur on the request.
	 * @see #setFaultStringOrReason(String)
	 */
	public static final String DEFAULT_FAULTSTRING_OR_REASON = "Validation error";

	private boolean addValidationErrorDetail = true;

	private QName detailElementName = DEFAULT_DETAIL_ELEMENT_NAME;

	private String faultStringOrReason = DEFAULT_FAULTSTRING_OR_REASON;

	private Locale faultStringOrReasonLocale = Locale.ENGLISH;

	/**
	 * Returns whether a SOAP Fault detail element should be created when a validation
	 * error occurs. This detail element will contain the exact validation errors. It is
	 * only added when the underlying message is a {@code SoapMessage}. Defaults to
	 * {@code true}.
	 * @see org.springframework.ws.soap.SoapFault#addFaultDetail()
	 */
	public boolean getAddValidationErrorDetail() {
		return this.addValidationErrorDetail;
	}

	/**
	 * Indicates whether a SOAP Fault detail element should be created when a validation
	 * error occurs. This detail element will contain the exact validation errors. It is
	 * only added when the underlying message is a {@code SoapMessage}. Defaults to
	 * {@code true}.
	 * @see org.springframework.ws.soap.SoapFault#addFaultDetail()
	 */
	public void setAddValidationErrorDetail(boolean addValidationErrorDetail) {
		this.addValidationErrorDetail = addValidationErrorDetail;
	}

	/**
	 * Returns the fault detail element name when validation errors occur on the request.
	 */
	public QName getDetailElementName() {
		return this.detailElementName;
	}

	/**
	 * Sets the fault detail element name when validation errors occur on the request.
	 * Defaults to {@code DEFAULT_DETAIL_ELEMENT_NAME}.
	 * @see #DEFAULT_DETAIL_ELEMENT_NAME
	 */
	public void setDetailElementName(QName detailElementName) {
		this.detailElementName = detailElementName;
	}

	/**
	 * Sets the SOAP {@code faultstring} or {@code Reason} used when validation errors
	 * occur on the request.
	 */
	public String getFaultStringOrReason() {
		return this.faultStringOrReason;
	}

	/**
	 * Sets the SOAP {@code faultstring} or {@code Reason} used when validation errors
	 * occur on the request. It is only added when the underlying message is a
	 * {@code SoapMessage}. Defaults to {@code DEFAULT_FAULTSTRING_OR_REASON}.
	 * @see #DEFAULT_FAULTSTRING_OR_REASON
	 */
	public void setFaultStringOrReason(String faultStringOrReason) {
		this.faultStringOrReason = faultStringOrReason;
	}

	/**
	 * Returns the SOAP fault reason locale used when validation errors occur on the
	 * request.
	 */
	public Locale getFaultStringOrReasonLocale() {
		return this.faultStringOrReasonLocale;
	}

	/**
	 * Sets the SOAP fault reason locale used when validation errors occur on the request.
	 * It is only added when the underlying message is a {@code SoapMessage}. Defaults to
	 * English.
	 * @see java.util.Locale#ENGLISH
	 */
	public void setFaultStringOrReasonLocale(Locale faultStringOrReasonLocale) {
		this.faultStringOrReasonLocale = faultStringOrReasonLocale;
	}

	/**
	 * Template method that is called when the request message contains validation errors.
	 * This implementation logs all errors, returns {@code false}, and creates a
	 * {@link SoapBody#addClientOrSenderFault(String,Locale) client or sender}
	 * {@link SoapFault}, adding a {@link SoapFaultDetail} with all errors if the
	 * {@code addValidationErrorDetail} property is {@code true}.
	 * @param messageContext the message context
	 * @param errors the validation errors
	 * @return {@code true} to continue processing the request, {@code false} (the
	 * default) otherwise
	 */
	@Override
	protected boolean handleRequestValidationErrors(MessageContext messageContext, SAXParseException[] errors)
			throws TransformerException {
		for (SAXParseException error : errors) {
			this.logger.warn("XML validation error on request: " + error.getMessage());
		}
		if (messageContext.getResponse() instanceof SoapMessage response) {
			SoapBody body = response.getSoapBody();
			SoapFault fault = body.addClientOrSenderFault(getFaultStringOrReason(), getFaultStringOrReasonLocale());
			if (getAddValidationErrorDetail()) {
				SoapFaultDetail detail = fault.addFaultDetail();
				for (SAXParseException error : errors) {
					SoapFaultDetailElement detailElement = detail.addFaultDetailElement(getDetailElementName());
					detailElement.addText(error.getMessage());
				}
			}
		}
		return false;
	}

}
