/*
 * 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.addressing.server;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.BeansException;
import org.springframework.util.Assert;
import org.springframework.ws.soap.addressing.core.MessageAddressingProperties;

/**
 * Abstract base class for WS-Addressing {@code Action}-mapped
 * {@link org.springframework.ws.server.EndpointMapping} implementations. Provides
 * infrastructure for mapping endpoints to actions.
 * <p>
 * By default, this mapping creates a {@code Action} for reply messages based on the
 * request message, plus the extra {@link #setOutputActionSuffix(String) suffix}, and a *
 * By default, this mapping creates a {@code Action} for reply messages based on the
 * request message, plus the extra {@link #setOutputActionSuffix(String) suffix}.
 *
 * @author Arjen Poutsma
 * @since 1.5.0
 */
public abstract class AbstractActionEndpointMapping extends AbstractAddressingEndpointMapping {

	/** The defaults suffix to add to the request {@code Action} for reply messages. */
	public static final String DEFAULT_OUTPUT_ACTION_SUFFIX = "Response";

	/** The defaults suffix to add to response {@code Action} for reply messages. */
	public static final String DEFAULT_FAULT_ACTION_SUFFIX = "Fault";

	// keys are action URIs, values are endpoints
	private final Map<URI, Object> endpointMap = new HashMap<>();

	private String outputActionSuffix = DEFAULT_OUTPUT_ACTION_SUFFIX;

	private String faultActionSuffix = DEFAULT_OUTPUT_ACTION_SUFFIX;

	/** Returns the suffix to add to request {@code Action}s for reply messages. */
	public String getOutputActionSuffix() {
		return this.outputActionSuffix;
	}

	/**
	 * Sets the suffix to add to request {@code Action}s for reply messages.
	 * @see #DEFAULT_OUTPUT_ACTION_SUFFIX
	 */
	public void setOutputActionSuffix(String outputActionSuffix) {
		Assert.hasText(outputActionSuffix, "'outputActionSuffix' must not be empty");
		this.outputActionSuffix = outputActionSuffix;
	}

	/** Returns the suffix to add to request {@code Action}s for reply fault messages. */
	public String getFaultActionSuffix() {
		return this.faultActionSuffix;
	}

	/**
	 * Sets the suffix to add to request {@code Action}s for reply fault messages.
	 * @see #DEFAULT_FAULT_ACTION_SUFFIX
	 */
	public void setFaultActionSuffix(String faultActionSuffix) {
		Assert.hasText(faultActionSuffix, "'faultActionSuffix' must not be empty");
		this.faultActionSuffix = faultActionSuffix;
	}

	@Override
	protected final Object getEndpointInternal(MessageAddressingProperties map) {
		URI action = map.getAction();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Looking up endpoint for action [" + action + "]");
		}
		Object endpoint = lookupEndpoint(action);
		if (endpoint != null) {
			URI endpointAddress = getEndpointAddress(endpoint);
			if (endpointAddress == null || endpointAddress.equals(map.getTo())) {
				return endpoint;
			}
		}
		return null;
	}

	/**
	 * Returns the address property of the given endpoint. The value of this property
	 * should match the {@link MessageAddressingProperties#getTo() destination} of
	 * incoming messages. May return {@code null} to ignore the destination.
	 * @param endpoint the endpoint to return the address for
	 * @return the endpoint address; or {@code null} to ignore the destination property
	 */
	protected abstract URI getEndpointAddress(Object endpoint);

	/**
	 * Looks up an endpoint instance for the given action. All keys are tried in order.
	 * @param action the action URI
	 * @return the associated endpoint instance, or {@code null} if not found
	 */
	protected Object lookupEndpoint(URI action) {
		return this.endpointMap.get(action);
	}

	/**
	 * Register the specified endpoint for the given action URI.
	 * @param action the action the bean should be mapped to
	 * @param endpoint the endpoint instance or endpoint bean name String (a bean name
	 * will automatically be resolved into the corresponding endpoint bean)
	 * @throws org.springframework.beans.BeansException if the endpoint couldn't be
	 * registered
	 * @throws IllegalStateException if there is a conflicting endpoint registered
	 */
	protected void registerEndpoint(URI action, Object endpoint) throws BeansException, IllegalStateException {
		Assert.notNull(action, "Action must not be null");
		Assert.notNull(endpoint, "Endpoint object must not be null");
		Object resolvedEndpoint = endpoint;

		if (endpoint instanceof String endpointName) {
			if (getApplicationContext().isSingleton(endpointName)) {
				resolvedEndpoint = getApplicationContext().getBean(endpointName);
			}
		}
		Object mappedEndpoint = this.endpointMap.get(action);
		if (mappedEndpoint != null) {
			if (mappedEndpoint != resolvedEndpoint) {
				throw new IllegalStateException("Cannot map endpoint [" + endpoint + "] to action [" + action
						+ "]: There is already endpoint [" + resolvedEndpoint + "] mapped.");
			}
		}
		else {
			this.endpointMap.put(action, resolvedEndpoint);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Mapped Action [" + action + "] onto endpoint [" + resolvedEndpoint + "]");
			}
		}
	}

	@Override
	protected URI getResponseAction(Object endpoint, MessageAddressingProperties requestMap) {
		URI requestAction = requestMap.getAction();
		if (requestAction != null) {
			return URI.create(requestAction + getOutputActionSuffix());
		}
		else {
			return null;
		}
	}

	@Override
	protected URI getFaultAction(Object endpoint, MessageAddressingProperties requestMap) {
		URI requestAction = requestMap.getAction();
		if (requestAction != null) {
			return URI.create(requestAction + getFaultActionSuffix());
		}
		else {
			return null;
		}
	}

}
