/*
 * 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.ws.extensions.policy.metadata;

import java.io.InputStream;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.ws.policy.Policy;
import org.apache.ws.policy.PolicyReference;
import org.apache.ws.policy.util.DOMPolicyReader;
import org.apache.ws.policy.util.PolicyFactory;
import org.apache.ws.policy.util.PolicyRegistry;
import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.extensions.policy.PolicyScopeLevel;
import org.jboss.ws.extensions.policy.annotation.PolicyAttachment;
import org.jboss.ws.extensions.policy.deployer.PolicyDeployer;
import org.jboss.ws.extensions.policy.deployer.exceptions.UnsupportedPolicy;
import org.jboss.ws.metadata.umdm.EndpointMetaData;
import org.jboss.ws.metadata.umdm.ExtensibleMetaData;
import org.jboss.ws.metadata.wsdl.WSDLBinding;
import org.jboss.ws.metadata.wsdl.WSDLDefinitions;
import org.jboss.ws.metadata.wsdl.WSDLEndpoint;
import org.jboss.ws.metadata.wsdl.WSDLExtensibilityElement;
import org.jboss.ws.metadata.wsdl.WSDLInterface;
import org.jboss.ws.metadata.wsdl.WSDLProperty;
import org.jboss.ws.metadata.wsdl.WSDLService;
import org.jboss.wsf.spi.deployment.UnifiedVirtualFile;

/**
 * A meta data builder for policies; handles checks for policy support
 * and their eventual deploy on both server side and client side.
 * 
 * @author Alessio Soldano, <mailto:alessio.soldano@javalinux.it>
 *
 * @since 16-May-2007
 */
public class PolicyMetaDataBuilder
{
   private static final Logger log = Logger.getLogger(PolicyMetaDataBuilder.class);
   private boolean serverSide = true;
   private boolean toolMode = false;
   private PolicyDeployer customDeployer;

   public PolicyMetaDataBuilder()
   {

   }

   /**
    * To be used for tests or whenever a custom deployer is required
    * 
    * @param customDeployer
    */
   public PolicyMetaDataBuilder(PolicyDeployer customDeployer)
   {
      this.customDeployer = customDeployer;
   }

   /**
    * Creates a new PolicyMetaDataBuilder for server side policy processing.
    * 
    * @param toolMode   True if running WSProvideTask (no policy deployments)
    * @return
    */
   public static PolicyMetaDataBuilder getServerSidePolicyMetaDataBuilder(boolean toolMode)
   {
      PolicyMetaDataBuilder builder = new PolicyMetaDataBuilder();
      builder.setServerSide(true);
      builder.setToolMode(toolMode);
      return builder;
   }

   /**
    * Creates a new PolicyMetaDataBuilder for client side policy processing.
    * 
    * @return
    */
   public static PolicyMetaDataBuilder getClientSidePolicyMetaDataBuilder()
   {
      PolicyMetaDataBuilder builder = new PolicyMetaDataBuilder();
      builder.setServerSide(false);
      return builder;
   }

   public void processPolicyAnnotations(EndpointMetaData epMetaData, Class<?> sepClass)
   {
      UnifiedVirtualFile vfRoot = epMetaData.getServiceMetaData().getUnifiedMetaData().getRootFile();
      for (org.jboss.ws.extensions.policy.annotation.Policy anPolicy : sepClass.getAnnotation(PolicyAttachment.class).value())
      {
         InputStream is = null;
         try
         {
            String policyFileLocation = anPolicy.policyFileLocation();
            if (policyFileLocation.length() == 0)
               throw new IllegalStateException("Cannot obtain @Policy.policyFileLocation");
            
            // The root virtual file is the uniform way to obtain resources
            // It should work in all containers, server/client side
            UnifiedVirtualFile vfPolicyFile = vfRoot.findChild(policyFileLocation);
            is = vfPolicyFile.toURL().openStream();
            
            DOMPolicyReader reader = (DOMPolicyReader)PolicyFactory.getPolicyReader(PolicyFactory.DOM_POLICY_READER);
            Policy unnormalizedPolicy = reader.readPolicy(is);
            Policy normPolicy = (Policy)unnormalizedPolicy.normalize();
            log.info("Deploying Annotated Policy = " + policyFileLocation);
            PolicyScopeLevel scope = anPolicy.scope();
            if (PolicyScopeLevel.WSDL_PORT.equals(scope) || PolicyScopeLevel.WSDL_PORT_TYPE.equals(scope) || PolicyScopeLevel.WSDL_BINDING.equals(scope))
            {
               deployPolicy(normPolicy, scope, epMetaData);
            }
            else
            {
               throw new WSException("Policy scope " + scope + " not supported yet!");
            }
         }
         catch (Exception e)
         {
            log.error(e);
         }
         finally
         {
            try
            {
               is.close();
            }
            catch (Exception e)
            {
            }
         }
      }
   }

   public void processPolicyExtensions(EndpointMetaData epMetaData, WSDLDefinitions wsdlDefinitions)
   {
      //Collect all policies defined in our wsdl definitions
      DOMPolicyReader reader = (DOMPolicyReader)PolicyFactory.getPolicyReader(PolicyFactory.DOM_POLICY_READER);
      PolicyRegistry localPolicyRegistry = new PolicyRegistry();
      for (WSDLExtensibilityElement policyElement : wsdlDefinitions.getExtensibilityElements(Constants.WSDL_ELEMENT_POLICY))
      {
         Policy policy = reader.readPolicy(policyElement.getElement());
         localPolicyRegistry.register(policy.getPolicyURI(), policy);
      }
      //Port scope
      WSDLService wsdlService = wsdlDefinitions.getService(epMetaData.getServiceMetaData().getServiceName());
      if (wsdlService != null)
      {
         WSDLEndpoint wsdlEndpoint = wsdlService.getEndpoint(epMetaData.getPortName());
         if (wsdlEndpoint != null)
         {
            List<WSDLExtensibilityElement> portPolicyRefList = wsdlEndpoint.getExtensibilityElements(Constants.WSDL_ELEMENT_POLICYREFERENCE);
            processPolicies(portPolicyRefList, PolicyScopeLevel.WSDL_PORT, localPolicyRegistry, epMetaData);
         }
         else
         {
            log
                  .warn("Cannot get port '" + epMetaData.getPortName()
                        + "' from the given wsdl definitions! Eventual policies attached to this port won't be considered.");
         }
      }
      else
      {
         log.warn("Cannot get service '" + epMetaData.getServiceMetaData().getServiceName()
               + "' from the given wsdl definitions!  Eventual policies attached to this service won't be considered.");
      }

      //Binding scope
      WSDLBinding wsdlBinding = wsdlDefinitions.getBindingByInterfaceName(epMetaData.getPortTypeName());
      if (wsdlBinding != null)
      {
         List<WSDLExtensibilityElement> bindingPolicyRefList = wsdlBinding.getExtensibilityElements(Constants.WSDL_ELEMENT_POLICYREFERENCE);
         processPolicies(bindingPolicyRefList, PolicyScopeLevel.WSDL_BINDING, localPolicyRegistry, epMetaData);
      }
      else
      {
         log.warn("Cannot get binding for portType '" + epMetaData.getPortTypeName()
               + "' from the given wsdl definitions!  Eventual policies attached to this binding won't be considered.");
      }

      //PortType scope
      WSDLInterface wsdlInterface = wsdlDefinitions.getInterface(epMetaData.getPortTypeName());
      if (wsdlInterface != null)
      {
         WSDLProperty portTypePolicyProp = wsdlInterface.getProperty(Constants.WSDL_PROPERTY_POLICYURIS);
         processPolicies(portTypePolicyProp, PolicyScopeLevel.WSDL_PORT_TYPE, localPolicyRegistry, epMetaData);
      }
      else
      {
         log.warn("Cannot get portType '" + epMetaData.getPortTypeName()
               + "' from the given wsdl definitions! Eventual policies attached to this portType won't be considered.");
      }
   }

   private void processPolicies(WSDLProperty policyProp, PolicyScopeLevel scope, PolicyRegistry localPolicies, ExtensibleMetaData extMetaData)
   {
      if (policyProp != null && policyProp.getValue() != null)
      {
         StringTokenizer st = new StringTokenizer(policyProp.getValue(), ", ", false);
         while (st.hasMoreTokens())
         {
            PolicyReference policyRef = new PolicyReference(st.nextToken());
            deployPolicy(resolvePolicyReference(policyRef, localPolicies), scope, extMetaData);
         }
      }
   }

   private void processPolicies(List<WSDLExtensibilityElement> policyReferences, PolicyScopeLevel scope, PolicyRegistry localPolicies, ExtensibleMetaData extMetaData)
   {
      if (policyReferences != null && policyReferences.size() != 0)
      {
         DOMPolicyReader reader = (DOMPolicyReader)PolicyFactory.getPolicyReader(PolicyFactory.DOM_POLICY_READER);
         for (WSDLExtensibilityElement element : policyReferences)
         {
            PolicyReference policyRef = reader.readPolicyReference(element.getElement());
            deployPolicy(resolvePolicyReference(policyRef, localPolicies), scope, extMetaData);
         }
      }
   }

   private Policy resolvePolicyReference(PolicyReference policyRef, PolicyRegistry localPolicies)
   {
      Policy normPolicy;
      try
      {
         normPolicy = (Policy)policyRef.normalize(localPolicies);
      }
      catch (RuntimeException e)
      {
         //TODO!!! not a local policy: get the policy definition and create the policy
         normPolicy = null;
      }
      return normPolicy;
   }

   private void deployPolicy(Policy policy, PolicyScopeLevel scope, ExtensibleMetaData extMetaData)
   {
      PolicyDeployer deployer;
      if (customDeployer != null)
      {
         deployer = customDeployer;
      }
      else if (toolMode)
      {
         deployer = PolicyDeployer.newInstanceForTools();
      }
      else
      {
         deployer = PolicyDeployer.getInstance();
      }
      if (serverSide)
      {
         deployPolicyServerSide(policy, scope, extMetaData, deployer);
      }
      else
      {
         deployPolicyClientSide(policy, scope, extMetaData, deployer);
      }
   }

   private void deployPolicyServerSide(Policy policy, PolicyScopeLevel scope, ExtensibleMetaData extMetaData, PolicyDeployer deployer)
   {
      PolicyMetaExtension ext = (PolicyMetaExtension)extMetaData.getExtension(Constants.URI_WS_POLICY);
      if (ext == null)
      {
         ext = new PolicyMetaExtension(Constants.URI_WS_POLICY);
         extMetaData.addExtension(ext);
      }
      try
      {
         Policy deployedPolicy = deployer.deployServerside(policy, extMetaData);
         ext.addPolicy(scope, deployedPolicy);
      }
      catch (UnsupportedPolicy e)
      {
         log.warn("Policy Not supported:" + policy.getPolicyURI());
      }
   }

   private void deployPolicyClientSide(Policy policy, PolicyScopeLevel scope, ExtensibleMetaData extMetaData, PolicyDeployer deployer)
   {
      PolicyMetaExtension ext = (PolicyMetaExtension)extMetaData.getExtension(Constants.URI_WS_POLICY);
      if (ext == null)
      {
         ext = new PolicyMetaExtension(Constants.URI_WS_POLICY);
         extMetaData.addExtension(ext);
      }
      try
      {
         deployer.deployClientSide(policy, extMetaData);
         ext.addPolicy(scope, policy);
      }
      catch (UnsupportedPolicy e)
      {
         if (log.isDebugEnabled())
         {
            log.debug("Policy Not supported:" + policy.getPolicyURI());
         }
         WSException.rethrow("Policy not supported! " + policy.getPolicyURI(), e);
      }
   }

   public boolean isServerSide()
   {
      return serverSide;
   }

   public void setServerSide(boolean serverSide)
   {
      this.serverSide = serverSide;
   }

   public boolean isToolMode()
   {
      return toolMode;
   }

   public void setToolMode(boolean toolMode)
   {
      this.toolMode = toolMode;
   }
}
