/*
 * 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.wsdl.wsdl11.provider;

import javax.wsdl.Definition;
import javax.wsdl.Message;
import javax.wsdl.Part;
import javax.wsdl.Types;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.schema.Schema;
import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.springframework.util.Assert;

/**
 * Default implementation of the {@link MessagesProvider}.
 * <p>
 * Simply adds all elements contained in the schema(s) as messages.
 *
 * @author Arjen Poutsma
 * @since 1.5.0
 */
public class DefaultMessagesProvider implements MessagesProvider {

	private static final Log logger = LogFactory.getLog(DefaultMessagesProvider.class);

	@Override
	public void addMessages(Definition definition) throws WSDLException {

		Types types = definition.getTypes();
		Assert.notNull(types, "No types element present in definition");
		for (Object element : types.getExtensibilityElements()) {
			ExtensibilityElement extensibilityElement = (ExtensibilityElement) element;
			if (extensibilityElement instanceof Schema schema) {
				if (schema.getElement() != null) {
					createMessages(definition, schema.getElement());
				}
			}
		}

		if (definition.getMessages().isEmpty() && logger.isWarnEnabled()) {
			logger.warn("No messages were created, make sure the referenced schema(s) contain elements");
		}
	}

	private void createMessages(Definition definition, Element schemaElement) throws WSDLException {

		String schemaTargetNamespace = schemaElement.getAttribute("targetNamespace");
		Assert.hasText(schemaTargetNamespace, "No targetNamespace defined on schema");

		if (logger.isDebugEnabled()) {
			logger.debug("Looking for elements in schema with target namespace [" + schemaTargetNamespace + "]");
		}

		NodeList children = schemaElement.getChildNodes();

		for (int i = 0; i < children.getLength(); i++) {

			Node child = children.item(i);

			if (child.getNodeType() == Node.ELEMENT_NODE) {
				Element childElement = (Element) child;
				if (isMessageElement(childElement)) {
					QName elementName = new QName(schemaTargetNamespace, getElementName(childElement));
					Message message = definition.createMessage();
					populateMessage(definition, message, elementName);
					Part part = definition.createPart();
					populatePart(definition, part, elementName);
					message.addPart(part);
					message.setUndefined(false);
					definition.addMessage(message);
				}
			}
		}
	}

	/**
	 * Returns the name attribute of the given element.
	 * @param element the element whose name to return
	 * @return the name of the element
	 */
	protected String getElementName(Element element) {
		return element.getAttribute("name");
	}

	/**
	 * Indicates whether the given element should be includes as {@link Message} in the
	 * definition.
	 * <p>
	 * Default implementation checks whether the element has the XML Schema namespace, and
	 * if it has the local name "element".
	 * @param element the element elligable for being a message
	 * @return {@code true} if to be included as message; {@code false} otherwise
	 */
	protected boolean isMessageElement(Element element) {

		return "element".equals(element.getLocalName())
				&& "http://www.w3.org/2001/XMLSchema".equals(element.getNamespaceURI());
	}

	/**
	 * Called after the {@link Message} has been created.
	 * <p>
	 * Default implementation sets the name of the message to the element name.
	 * @param definition the WSDL4J {@code Definition}
	 * @param message the WSDL4J {@code Message}
	 * @param elementName the element name
	 * @throws WSDLException in case of errors
	 */
	protected void populateMessage(Definition definition, Message message, QName elementName) throws WSDLException {

		QName messageName = new QName(definition.getTargetNamespace(), elementName.getLocalPart());
		if (logger.isDebugEnabled()) {
			logger.debug("Creating message [" + messageName + "]");
		}
		message.setQName(messageName);
	}

	/**
	 * Called after the {@link Part} has been created.
	 * <p>
	 * Default implementation sets the element name of the part.
	 * @param definition the WSDL4J {@code Definition}
	 * @param part the WSDL4J {@code Part}
	 * @param elementName the elementName @throws WSDLException in case of errors
	 * @see Part#setElementName(javax.xml.namespace.QName)
	 */
	protected void populatePart(Definition definition, Part part, QName elementName) throws WSDLException {

		part.setElementName(elementName);
		part.setName(elementName.getLocalPart());
	}

}
