/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.security.auth.message.config;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;

import org.jboss.security.PicketBoxMessages;

//$Id$

/**
 * Default Authentication Configuration Factory
 * 
 * @author <a href="mailto:Anil.Saldhana@jboss.org">Anil Saldhana</a>
 * @since May 15, 2006
 * @version $Revision$
 */
public class JBossAuthConfigFactory extends AuthConfigFactory
{
   /**
    * Map of String key to provider.
    */
   private ConcurrentMap<String, AuthConfigProvider> keyToAuthConfigProviderMap = new ConcurrentHashMap<String, AuthConfigProvider>();

   /**
    * Map of key to listener.
    */
   private ConcurrentMap<String, RegistrationListener> keyToRegistrationListenerMap = new ConcurrentHashMap<String, RegistrationListener>();

   /**
    * Map of key to registration context.
    */
   private ConcurrentMap<String, RegistrationContext> keyToRegistrationContextMap = new ConcurrentHashMap<String, RegistrationContext>();
    
   /**
    * <p>
    * Creates an instance of {@code JBossAuthConfigFactory}.
    * </p>
    */
   public JBossAuthConfigFactory()
   {
      Map<String, Object> props = new HashMap<String, Object>();
      JBossAuthConfigProvider provider = new JBossAuthConfigProvider(props, null);
      // register a few default providers for the layers
      this.registerConfigProvider(provider, "HTTP", null, "Default Provider");
      this.registerConfigProvider(provider, "HttpServlet", null, "Default Provider");
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#detachListener(javax.security.auth.message.config.RegistrationListener, java.lang.String, java.lang.String)
    */
   public String[] detachListener(RegistrationListener listener, String layer, String appContext)
   { 
      if (listener == null)
         throw PicketBoxMessages.MESSAGES.invalidNullArgument("listener");

      String[] arr = new String[0];
      String input = layer + appContext;
      String allLayer = "null" + appContext;
      String allContext = layer + "null";
      String general = "nullnull";

      RegistrationListener origListener = null;
      String key = null;
      for (int i = 0; i < 4 && origListener == null; i++)
      {
         if (i == 0)
            key = input;
         if (i == 1)
            key = allLayer;
         if (i == 2)
            key = allContext;
         if (i == 3)
            key = general;
         origListener = keyToRegistrationListenerMap.get(key);
      }

      if (origListener == listener)
      {
          keyToRegistrationListenerMap.remove(key, listener);
         // Get the ID List
      }
      return arr;
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#getConfigProvider(java.lang.String, java.lang.String, javax.security.auth.message.config.RegistrationListener)
    */
   public AuthConfigProvider getConfigProvider(String layer, String appContext, RegistrationListener listener)
   {
      String input = new StringBuilder().append(layer).append(appContext).toString();
      String allLayer = "null" + appContext;
      String allContext = layer + "null";
      String general = "nullnull";

      AuthConfigProvider acp = null;
      String key = null;
      for (int i = 0; i < 4 && acp == null; i++)
      {
         if (i == 0)
            key = input;
         if (i == 1)
            key = allLayer;
         if (i == 2)
            key = allContext;
         if (i == 3)
            key = general;

         acp = this.keyToAuthConfigProviderMap.get(key);
      }

      //
      if (listener != null)
         this.keyToRegistrationListenerMap.put(input, listener);

      return acp;
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#getRegistrationContext(java.lang.String)
    */
   public RegistrationContext getRegistrationContext(String registrationID)
   {
      return this.keyToRegistrationContextMap.get(registrationID);
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#getRegistrationIDs(javax.security.auth.message.config.AuthConfigProvider)
    */
   public String[] getRegistrationIDs(AuthConfigProvider provider)
   {
      List<String> al = new ArrayList<String>();
      if (provider == null)
      {
         al.addAll(keyToAuthConfigProviderMap.keySet());
      }
      else
      {
         // get all entries that have the supplied provider as value and store their keys.
         for (Map.Entry<String, AuthConfigProvider> entry : this.keyToAuthConfigProviderMap.entrySet())
         {
            if (entry.getValue().equals(provider))
               al.add(entry.getKey());
         }
      }
      String[] sarr = new String[al.size()];
      al.toArray(sarr);
      return sarr;
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#refresh()
    */
   public void refresh()
   {
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#registerConfigProvider(java.lang.String, java.util.Map, java.lang.String, java.lang.String, java.lang.String)
    */
   public String registerConfigProvider(String className, Map properties, String layer, String appContext,
         String description)
   {
      // Instantiate the provider
      AuthConfigProvider acp = null;
      if (className != null) {
         try
         {
            // An AuthConfigProvider must have a two-argument constructor (Map properties, AuthConfigFactory factory).
            Class<?> provClass = SecurityActions.getContextClassLoader().loadClass(className);
            Constructor<?> ctr = provClass.getConstructor(new Class[] {Map.class, AuthConfigFactory.class});
            acp = (AuthConfigProvider) ctr.newInstance(new Object[] {properties, null});
         }
         catch (Exception e)
         {
            throw PicketBoxMessages.MESSAGES.failedToRegisterAuthConfigProvider(className, e);
         }
      }

      // build the provider registration id using layer + appContext, which is a unique pair.
      String registrationID = new StringBuilder().append(layer).append(appContext).toString();
      
      // check if we already have a registration for the layer/appContext key.
      AuthConfigProvider oldProvider = this.keyToAuthConfigProviderMap.put(registrationID, acp);
      if (oldProvider != null)
      {
         // registration already exists and provider has been replaced. Update the registration context.
         JBossRegistrationContext context = (JBossRegistrationContext) this.keyToRegistrationContextMap.get(registrationID);
         context.setDescription(description);
         context.setIsPersistent(true);
         // if there is a listener attached to the registration, notify it that the registration has been replaced.
         RegistrationListener listener = this.keyToRegistrationListenerMap.get(registrationID);
         if (listener != null)
            listener.notify(layer, appContext);
      }
      else
      {
         // create a registration context for the new registration.
         RegistrationContext context = new JBossRegistrationContext(layer, appContext, description, true);
         this.keyToRegistrationContextMap.put(registrationID, context);
      }

      return registrationID;
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#registerConfigProvider(javax.security.auth.message.config.AuthConfigProvider, java.lang.String, java.lang.String, java.lang.String)
    */
   public String registerConfigProvider(AuthConfigProvider provider, String layer, String appContext, String description)
   {
      String registrationID = new StringBuilder().append(layer).append(appContext).toString();

       // check if we already have a registration for the layer/appContext key.
      AuthConfigProvider oldProvider = this.keyToAuthConfigProviderMap.put(registrationID, provider);
      if (oldProvider != null)
      {
          // registration already exists and provider has been replaced. Update the registration context.
          JBossRegistrationContext context = (JBossRegistrationContext) this.keyToRegistrationContextMap.get(registrationID);
          context.setDescription(description);
          context.setIsPersistent(false);
          // if there is a listener attached to the registration, notify it that the registration has been replaced.
          RegistrationListener listener = this.keyToRegistrationListenerMap.get(registrationID);
          if (listener != null)
              listener.notify(layer, appContext);
      }
      else
      {
         // create a registration context for the new registration.
          RegistrationContext context = new JBossRegistrationContext(layer, appContext, description, false);
          this.keyToRegistrationContextMap.put(registrationID, context);
      }

      return registrationID;
   }

   /*
    * (non-Javadoc)
    * @see javax.security.auth.message.config.AuthConfigFactory#removeRegistration(java.lang.String)
    */
   public boolean removeRegistration(String registrationID)
   {
      if (registrationID == null)
         throw PicketBoxMessages.MESSAGES.invalidNullArgument("registrationID");

      RegistrationListener listener = this.keyToRegistrationListenerMap.get(registrationID);
      RegistrationContext rc = this.keyToRegistrationContextMap.get(registrationID);

      // remove the provider and notify listener of the change.
      boolean removed = (this.keyToAuthConfigProviderMap.remove(registrationID) != null);
      if (removed && listener != null)
         listener.notify(rc.getMessageLayer(), rc.getAppContext());
      this.keyToRegistrationContextMap.remove(registrationID);

      return removed;
   }

   static class JBossRegistrationContext implements RegistrationContext {

       private String messageLayer;

       private String appContext;

       private String description;

       private boolean isPersistent;

       JBossRegistrationContext(String layer, String appContext, String description, boolean isPersistent)
       {
          this.messageLayer = layer;
          this.appContext = appContext;
          this.description = description;
          this.isPersistent = isPersistent;
       }

       public String getAppContext()
       {
          return this.appContext;
       }
       
       public void setAppContext(String appContext)
       {
          this.appContext = appContext;
       }

       public String getDescription()
       {
          return this.description;
       }
       
       public void setDescription(String description)
       {
          this.description = description;
       }

       public String getMessageLayer()
       {
          return this.messageLayer;
       }

       public void setMessageLayer(String messageLayer)
       {
          this.messageLayer = messageLayer;
       }

       public boolean isPersistent()
       {
          return this.isPersistent;
       }

       public void setIsPersistent(boolean isPersistent)
       {
          this.isPersistent = isPersistent;
       }
   }       
}