/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2009, 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.webservices.integration.deployers;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.Context;

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.deployer.helpers.AbstractRealDeployer;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.ejb.deployers.EjbDeployment;
import org.jboss.ejb.deployers.MergedJBossMetaDataDeployer;
import org.jboss.ejb3.EJBContainer;
import org.jboss.ejb3.Ejb3Deployment;
import org.jboss.metadata.ejb.jboss.JBossEnterpriseBeanMetaData;
import org.jboss.metadata.ejb.jboss.JBossMetaData;
import org.jboss.webservices.integration.util.ASHelper;
import org.jboss.wsf.spi.deployment.integration.WebServiceDeclaration;
import org.jboss.wsf.spi.deployment.integration.WebServiceDeployment;
import org.jboss.wsf.spi.metadata.webservices.WebservicesMetaData;

/**
 * WebServiceDeployment deployer processes EJB containers and its metadata and creates WS adapters wrapping it.
 *
 * @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a>
 */
public final class WSEJBAdapterDeployer extends AbstractRealDeployer
{

   /**
    * Constructor.
    */
   public WSEJBAdapterDeployer()
   {
      super();

      // inputs
      this.addInput(MergedJBossMetaDataDeployer.EJB_MERGED_ATTACHMENT_NAME);
      this.addInput(EjbDeployment.class);
      this.addInput(Ejb3Deployment.class);
      this.addInput(WebservicesMetaData.class);

      // outputs
      this.addOutput(WebServiceDeployment.class);
   }

   /**
    * Deploys WebServiceDeployment meta data.
    * 
    * @param unit deployment unit
    * @throws DeploymentException exception
    */
   @Override
   protected void internalDeploy(final DeploymentUnit unit) throws DeploymentException
   {
      final JBossMetaData mergedMD = (JBossMetaData) unit
            .getAttachment(MergedJBossMetaDataDeployer.EJB_MERGED_ATTACHMENT_NAME);
      final Ejb3Deployment ejb3Deployment = ASHelper.getOptionalAttachment(unit, Ejb3Deployment.class);

      if (mergedMD != null)
      {
         final WebServiceDeploymentAdapter wsDeploymentAdapter = new WebServiceDeploymentAdapter();
         final List<WebServiceDeclaration> endpoints = wsDeploymentAdapter.getServiceEndpoints();

         for (final JBossEnterpriseBeanMetaData ejbMD : mergedMD.getEnterpriseBeans())
         {
            final String ejbName = ejbMD.determineContainerName();

            if (ejbMD.getEjbClass() != null)
            {
               this.log.debug("Creating webservice EJB adapter for: " + ejbName);
               final EJBContainer ejbContainer = this.getContainer(ejb3Deployment, ejbMD);
               endpoints.add(new WebServiceDeclarationAdapter(ejbMD, ejbContainer, unit.getClassLoader()));
            }
            else
            {
               this.log.warn("Ingoring EJB deployment with null classname: " + ejbName);
            }
         }

         unit.addAttachment(WebServiceDeployment.class, wsDeploymentAdapter);
      }
   }

   /**
    * Returns EJB container if EJB3 deployment is detected and EJB meta data does not represent entity bean.
    * 
    * @param ejb3Deployment EJB3 deployment meta data
    * @param ejbMD EJB meta data
    * @return EJB container or null if not EJB3 stateless bean
    * @throws DeploymentException if some error occurs
    */
   private EJBContainer getContainer(final Ejb3Deployment ejb3Deployment, final JBossEnterpriseBeanMetaData ejbMD)
         throws DeploymentException
   {
      if ((ejb3Deployment != null) && (!ejbMD.isEntity()))
      {
         try
         {
            final ObjectName objName = new ObjectName(ejbMD.determineContainerName());
            return (EJBContainer) ejb3Deployment.getContainer(objName);
         }
         catch (MalformedObjectNameException e)
         {
            throw new DeploymentException(e);
         }
      }

      return null;
   }

   /**
    * Adopts EJB3 bean meta data to a
    * {@link org.jboss.wsf.spi.deployment.integration.WebServiceDeclaration}.
    */
   private static final class WebServiceDeclarationAdapter implements WebServiceDeclaration
   {

      /** EJB meta data. */
      private final JBossEnterpriseBeanMetaData ejbMetaData;

      /** EJB container. */
      private final EJBContainer ejbContainer;

      /** Class loader. */
      private final ClassLoader loader;

      /**
       * Constructor.
       * 
       * @param ejbMetaData EJB metadata
       * @param ejbContainer EJB container
       * @param loader class loader
       */
      private WebServiceDeclarationAdapter(final JBossEnterpriseBeanMetaData ejbMetaData,
            final EJBContainer ejbContainer, final ClassLoader loader)
      {
         super();

         this.ejbMetaData = ejbMetaData;
         this.ejbContainer = ejbContainer;
         this.loader = loader;
      }

      /**
       * Returns EJB container name.
       *
       * @return container name
       */
      public String getContainerName()
      {
         return this.ejbMetaData.determineContainerName();
      }

      /**
       * Returns JNDI context associated with EJB container.
       *
       * @return JNDI context
       */
      public Context getContext()
      {
         return this.ejbContainer.getEnc();
      }

      /**
       * Returns EJB name.
       *
       * @return name
       */
      public String getComponentName()
      {
         return this.ejbMetaData.getName();
      }

      /**
       * Returns EJB class name.
       *
       * @return class name
       */
      public String getComponentClassName()
      {
         return this.ejbMetaData.getEjbClass();
      }

      /**
       * Returns requested annotation associated with EJB container or EJB bean.
       *
       * @param annotationType annotation type
       * @param <T> annotation class type
       * @return requested annotation or null if not found
       */
      public <T extends Annotation> T getAnnotation(final Class<T> annotationType)
      {
         final boolean haveEjbContainer = this.ejbContainer != null;

         if (haveEjbContainer)
         {
            return this.ejbContainer.getAnnotation(annotationType);
         }
         else
         {
            final Class<?> bean = this.getComponentClass();
            return (T) bean.getAnnotation(annotationType);
         }
      }

      /**
       * Loads ejb class from associated loader.
       *
       * @return ejb class instance
       */
      private Class<?> getComponentClass()
      {
         try
         {
            return this.loader.loadClass(this.getComponentClassName());
         }
         catch (ClassNotFoundException cnfe)
         {
            throw new RuntimeException("Failed to load component class: " + this.getComponentClassName()
                  + " from loader: " + this.loader);
         }
      }

   }

   /**
    * Adopts an EJB deployment to a 
    * {@link org.jboss.wsf.spi.deployment.integration.WebServiceDeployment}. 
    */
   private static final class WebServiceDeploymentAdapter implements WebServiceDeployment
   {

      /** List of endpoints. */
      private final List<WebServiceDeclaration> endpoints = new ArrayList<WebServiceDeclaration>();

      /**
       * Constructor.
       */
      private WebServiceDeploymentAdapter()
      {
         super();
      }

      /**
       * Returns endpoints list.
       * 
       * @return endpoints list
       */
      public List<WebServiceDeclaration> getServiceEndpoints()
      {
         return this.endpoints;
      }

   }

}
