/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import org.opends.messages.Message;



import java.util.LinkedHashSet;

import org.opends.server.api.DirectoryThread;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchListener;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.LDAPURL;
import org.opends.server.types.MembershipException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
import org.opends.server.types.SearchScope;

import static org.opends.messages.ExtensionMessages.*;
import org.opends.server.loggers.ErrorLogger;

/**
 * This class implements a Directory Server thread that will be used to perform
 * a background search to retrieve all of the members of a dynamic group.
 * <BR><BR>
 */
public class DynamicGroupSearchThread
// FIXME -- Would it be better to implement this class using an Executor
//          rather than always creating a custom thread?
       extends DirectoryThread
       implements InternalSearchListener
{
  // The set of base DNs for the search requests.
  private final DN[] baseDNs;

  // The member list with which this search thread is associated.
  private final DynamicGroupMemberList memberList;

  // A counter used to keep track of which search is currently in progress.
  private int searchCounter;

  // The set of member URLs for determining whether entries match the criteria.
  private final LDAPURL[][] memberURLs;

  // The set of search filters for the search requests.
  private final SearchFilter[] searchFilters;



  /**
   * Creates a new dynamic group search thread that is associated with the
   * provided member list and that will perform the search using the provided
   * information.
   *
   * @param  memberList  The dynamic group member list with which this thread is
   *                     associated.
   * @param  baseDNs     The set of base DNs to use for the search requests.
   * @param  filters     The set of search filters to use for the search
   *                     requests.
   * @param  memberURLs  The set of member URLs to use when determining if
   *                     entries match the necessary group criteria.
   */
  public DynamicGroupSearchThread(DynamicGroupMemberList memberList,
                                  DN[] baseDNs, SearchFilter[] filters,
                                  LDAPURL[][] memberURLs)
  {
    super("Dynamic Group Search Thread " + memberList.getDynamicGroupDN());

    this.memberList    = memberList;
    this.baseDNs       = baseDNs;
    this.searchFilters = filters;
    this.memberURLs    = memberURLs;

    searchCounter = 0;
  }



  /**
   * Performs the set of searches and provides the results to the associated
   * member list.
   */
  public void run()
  {
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    LinkedHashSet<String> attributes = new LinkedHashSet<String>(0);
    //Include all the user attributes along with the ismemberof.
    attributes.add("*");
    attributes.add("ismemberof");
    for (searchCounter = 0; searchCounter < baseDNs.length; searchCounter++)
    {
      InternalSearchOperation searchOperation =
           conn.processSearch(baseDNs[searchCounter], SearchScope.WHOLE_SUBTREE,
                              DereferencePolicy.NEVER_DEREF_ALIASES, 0, 0,
                              false, searchFilters[searchCounter], attributes,
                              this);

      ResultCode resultCode = searchOperation.getResultCode();
      if (resultCode != ResultCode.SUCCESS)
      {
        if (resultCode == ResultCode.NO_SUCH_OBJECT)
        {
          Message message = WARN_DYNAMICGROUP_NONEXISTENT_BASE_DN.
              get(String.valueOf(baseDNs[searchCounter]),
                  String.valueOf(memberList.getDynamicGroupDN()));
          ErrorLogger.logError(message);
          continue;
        }
        else
        {
          Message message =
               ERR_DYNAMICGROUP_INTERNAL_SEARCH_FAILED.get(
                       String.valueOf(baseDNs[searchCounter]),
                       String.valueOf(searchFilters[searchCounter]),
                       String.valueOf(memberList.getDynamicGroupDN()),
                       String.valueOf(resultCode),
                       String.valueOf(searchOperation.getErrorMessage()));
          if (! memberList.addResult(
                     new MembershipException(message, true)))
          {
            memberList.setSearchesCompleted();
            return;
          }
        }
      }
    }

    memberList.setSearchesCompleted();
  }



  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchEntry(InternalSearchOperation searchOperation,
                                        SearchResultEntry searchEntry)
         throws DirectoryException
  {
    for (LDAPURL url : memberURLs[searchCounter])
    {
      if (url.matchesEntry(searchEntry))
      {
        if (! memberList.addResult(searchEntry))
        {
          Message message = ERR_DYNAMICGROUP_CANNOT_RETURN_ENTRY.
              get(String.valueOf(searchEntry.getDN()),
                  String.valueOf(memberList.getDynamicGroupDN()));
          throw new DirectoryException(
                         DirectoryServer.getServerErrorResultCode(), message);
        }

        return;
      }
    }
  }



  /**
   * {@inheritDoc}
   */
  public void handleInternalSearchReference(
                   InternalSearchOperation searchOperation,
                   SearchResultReference searchReference)
  {
    // No implementation required.
  }
}

