/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * 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
 *
 *      http://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.security.ldap.authentication;

import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.util.Assert;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Base class for the authenticator implementations.
 *
 * @author Luke Taylor
 */
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator,
		InitializingBean, MessageSourceAware {
	// ~ Instance fields
	// ================================================================================================

	private final ContextSource contextSource;

	/**
	 * Optional search object which can be used to locate a user when a simple DN match
	 * isn't sufficient
	 */
	private LdapUserSearch userSearch;
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	/**
	 * The attributes which will be retrieved from the directory. Null means all
	 * attributes
	 */
	private String[] userAttributes = null;

	// private String[] userDnPattern = null;
	/** Stores the patterns which are used as potential DN matches */
	private MessageFormat[] userDnFormat = null;

	// ~ Constructors
	// ===================================================================================================

	/**
	 * Create an initialized instance with the {@link ContextSource} provided.
	 *
	 * @param contextSource
	 */
	public AbstractLdapAuthenticator(ContextSource contextSource) {
		Assert.notNull(contextSource, "contextSource must not be null.");
		this.contextSource = contextSource;
	}

	// ~ Methods
	// ========================================================================================================

	public void afterPropertiesSet() throws Exception {
		Assert.isTrue((userDnFormat != null) || (userSearch != null),
				"Either an LdapUserSearch or DN pattern (or both) must be supplied.");
	}

	protected ContextSource getContextSource() {
		return contextSource;
	}

	public String[] getUserAttributes() {
		return userAttributes;
	}

	/**
	 * Builds list of possible DNs for the user, worked out from the
	 * <tt>userDnPatterns</tt> property.
	 *
	 * @param username the user's login name
	 *
	 * @return the list of possible DN matches, empty if <tt>userDnPatterns</tt> wasn't
	 * set.
	 */
	protected List<String> getUserDns(String username) {
		if (userDnFormat == null) {
			return Collections.emptyList();
		}

		List<String> userDns = new ArrayList<>(userDnFormat.length);
		String[] args = new String[] { LdapEncoder.nameEncode(username) };

		synchronized (userDnFormat) {
			for (MessageFormat formatter : userDnFormat) {
				userDns.add(formatter.format(args));
			}
		}

		return userDns;
	}

	protected LdapUserSearch getUserSearch() {
		return userSearch;
	}

	public void setMessageSource(MessageSource messageSource) {
		Assert.notNull(messageSource, "Message source must not be null");
		this.messages = new MessageSourceAccessor(messageSource);
	}

	/**
	 * Sets the user attributes which will be retrieved from the directory.
	 *
	 * @param userAttributes
	 */
	public void setUserAttributes(String[] userAttributes) {
		Assert.notNull(userAttributes,
				"The userAttributes property cannot be set to null");
		this.userAttributes = userAttributes;
	}

	/**
	 * Sets the pattern which will be used to supply a DN for the user. The pattern should
	 * be the name relative to the root DN. The pattern argument {0} will contain the
	 * username. An example would be "cn={0},ou=people".
	 *
	 * @param dnPattern the array of patterns which will be tried when converting a
	 * username to a DN.
	 */
	public void setUserDnPatterns(String[] dnPattern) {
		Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");
		// this.userDnPattern = dnPattern;
		userDnFormat = new MessageFormat[dnPattern.length];

		for (int i = 0; i < dnPattern.length; i++) {
			userDnFormat[i] = new MessageFormat(dnPattern[i]);
		}
	}

	public void setUserSearch(LdapUserSearch userSearch) {
		Assert.notNull(userSearch, "The userSearch cannot be set to null");
		this.userSearch = userSearch;
	}
}
