/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file 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.deployment.security;

import java.util.ArrayList;

import javax.management.ObjectName;
import javax.xml.bind.JAXBElement;

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.deployer.DeploymentStages;
import org.jboss.deployers.spi.deployer.helpers.AbstractDeployer;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.metadata.javaee.support.IdMetaData;
import org.jboss.security.acl.config.ACLConfiguration;
import org.jboss.security.authorization.PolicyRegistration;
import org.jboss.system.metadata.ServiceAttributeMetaData;
import org.jboss.system.metadata.ServiceConstructorMetaData;
import org.jboss.system.metadata.ServiceDependencyMetaData;
import org.jboss.system.metadata.ServiceDependencyValueMetaData;
import org.jboss.system.metadata.ServiceMetaData;

/**
 * Abstract Security Deployer Sets up the top level JaccPolicy service beans
 * 
 * @author Anil.Saldhana@redhat.com
 * @since Feb 18, 2008
 * @version $Revision: 86077 $
 */
public abstract class AbstractSecurityDeployer<T extends IdMetaData> extends AbstractDeployer
{
   private static final String JACC_ATTACHMENT_NAME = "jboss.jacc";

   private static final String BASE_OBJECT_NAME = "jboss.jacc:service=jacc,id=";

   /** Attachment name for the JAXB model for xacml config - match the one from XacmlConfigParsingDeployer */
   private static final String XACML_ATTACHMENT_NAME = "xacml.config";  

   private PolicyRegistration policyRegistration;

   /**
    * 
    * @return
    */
   public PolicyRegistration getPolicyRegistration()
   {
      return this.policyRegistration;
   }

   /**
    * 
    * @param policyRegistration
    */
   public void setPolicyRegistration(PolicyRegistration policyRegistration)
   {
      this.policyRegistration = policyRegistration;
   }

   public AbstractSecurityDeployer()
   {
      setStage(DeploymentStages.POST_CLASSLOADER);
      setInput(getMetaDataClassType());
      setOutput(ServiceMetaData.class);
      addOutput(JACC_ATTACHMENT_NAME);
   }

   public void deploy(DeploymentUnit unit) throws DeploymentException
   {
      T metaData = unit.getAttachment(getMetaDataClassType());

      if (metaData == null)
         return;

      String contextId = unit.getSimpleName();

      // Is the war the top level deployment?
      // DeploymentUnit topUnit = unit.getTopLevel();
      if (unit.getParent() == null || getParentJaccPolicyBean(unit) == null)
      {
         createTopLevelServiceBeanWithMetaData(contextId, unit, metaData);
      }
      else
      {
         ServiceMetaData subjaccPolicy = getServiceMetaData();

         String deploymentName = unit.getSimpleName();

         try
         {
            subjaccPolicy.setObjectName(new ObjectName(getObjectName(unit)));
         }
         catch (Exception e)
         {
            throw new RuntimeException(e);
         }
         // Provide a constructor for the service bean
         ServiceConstructorMetaData serviceConstructor = new ServiceConstructorMetaData();
         serviceConstructor.setSignature(new String[]{String.class.getName(), getMetaDataClassType().getName()});
         serviceConstructor.setParameters(new Object[]{deploymentName, metaData});
         subjaccPolicy.setConstructor(serviceConstructor);

         ArrayList<ServiceMetaData> services = new ArrayList<ServiceMetaData>();
         services.add(subjaccPolicy);
         unit.addAttachment(JACC_ATTACHMENT_NAME, subjaccPolicy, ServiceMetaData.class);

         // Add a dependence into the parent JaccPolicy
         ServiceMetaData parentServiceMetaData = this.getParentJaccPolicyBean(unit);
         if (parentServiceMetaData != null)
         {
            ServiceDependencyMetaData serviceDependencyMetaData = new ServiceDependencyMetaData();
            serviceDependencyMetaData.setIDependOnObjectName(subjaccPolicy.getObjectName());
            parentServiceMetaData.addDependency(serviceDependencyMetaData);

            // Add an attribute in the parent service
            ServiceAttributeMetaData serviceAttributeMetaData = new ServiceAttributeMetaData();
            serviceAttributeMetaData.setName("PolicyConfigurationFacadeMBean");
            ServiceDependencyValueMetaData dependencyValue = new ServiceDependencyValueMetaData();
            dependencyValue.setDependency(subjaccPolicy.getObjectName().toString());
            dependencyValue.setProxyType("attribute");
            serviceAttributeMetaData.setValue(dependencyValue);
            parentServiceMetaData.addAttribute(serviceAttributeMetaData);
         }
      }
      
      /** Register XACML/ACL policies if present in the deployment */
      if(this.policyRegistration != null)
      {
         String xacmlType = PolicyRegistration.XACML;
         JAXBElement<?> policyConfig = (JAXBElement<?>) unit.getAttachment(XACML_ATTACHMENT_NAME);
         if(policyConfig != null)
            this.policyRegistration.registerPolicyConfig(contextId, xacmlType, policyConfig);
         
         String aclType = PolicyRegistration.ACL;
         ACLConfiguration aclConfig = (ACLConfiguration) unit.getAttachment(ACLConfiguration.class.getName());
         if(aclConfig != null)
            this.policyRegistration.registerPolicyConfig(contextId, aclType, aclConfig);   
      } 
   }

   @Override
   public void undeploy(DeploymentUnit unit)
   {
      unit.removeAttachment(JACC_ATTACHMENT_NAME);
      // unregister any XACML or ACL policies associated with the deployment unit.
      String contextId = unit.getSimpleName();
      if (this.policyRegistration != null)
      {
         this.policyRegistration.deRegisterPolicy(contextId, PolicyRegistration.XACML);
         this.policyRegistration.deRegisterPolicy(contextId, PolicyRegistration.ACL);
      }
   }

   private void createTopLevelServiceBeanWithMetaData(String contextId, DeploymentUnit unit, T deployment)
   {
      // Provide a constructor for the service bean
      ServiceConstructorMetaData serviceConstructor = new ServiceConstructorMetaData();
      serviceConstructor.setSignature(new String[]{String.class.getName(), this.getMetaDataClassType().getName(),
            Boolean.class.getName()});
      serviceConstructor.setParameters(new Object[]{contextId, deployment, Boolean.TRUE});
      createJaccPolicyBean(serviceConstructor, unit);
   }

   private void createJaccPolicyBean(ServiceConstructorMetaData serviceConstructor, DeploymentUnit unit)
   {
      // Create a Service Bean for the JACC Policy
      ServiceMetaData jaccPolicy = new ServiceMetaData();
      jaccPolicy.setCode(getJaccPolicyName());
      try
      {
         jaccPolicy.setObjectName(new ObjectName(getObjectName(unit)));
      }
      catch (Exception e)
      {
         throw new RuntimeException(e);
      }
      // Provide a constructor for the service bean
      jaccPolicy.setConstructor(serviceConstructor);
      ArrayList<ServiceMetaData> services = new ArrayList<ServiceMetaData>();
      services.add(jaccPolicy);

      unit.addAttachment(JACC_ATTACHMENT_NAME, jaccPolicy, ServiceMetaData.class);
   }

   private ServiceMetaData getParentJaccPolicyBean(DeploymentUnit childDU)
   {
      DeploymentUnit parentDU = childDU.getParent();

      while (parentDU != null)
      {
         ServiceMetaData parentJacc = (ServiceMetaData) parentDU.getAttachment(JACC_ATTACHMENT_NAME);
         if (parentJacc != null)
            return parentJacc;
         parentDU = parentDU.getParent();
      }
      return null;
   }

   /**
    * Get the name of the JaccPolicy subclass (EjbJaccPolicy,WebJaccPolicy etc)
    * 
    * @return
    */
   protected abstract String getJaccPolicyName();

   /**
    * Return the type of metadata
    * 
    * @return
    */
   protected abstract Class<T> getMetaDataClassType();

   /**
    * Get the top level service bean meta data
    * 
    * @return
    */
   protected abstract ServiceMetaData getServiceMetaData();

   /**
    * Qualify the object name with parent name just to avoid conflicts with deployments with the same name in multiple
    * archives
    */
   private String getObjectName(DeploymentUnit unit)
   {
      String deploymentName = unit.getName();
      DeploymentUnit parentDU = unit.getParent();
      String parentDeploymentName = parentDU != null ? ",parent=\"" + parentDU.getSimpleName() + "\"" : "";
      return BASE_OBJECT_NAME + "\"" + deploymentName + "\"" + parentDeploymentName;
   }
}
