/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.cxf.ws.policy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ResourceBundle;

import org.w3c.dom.Element;

import org.apache.cxf.Bus;
import org.apache.cxf.common.i18n.BundleUtils;
import org.apache.cxf.common.i18n.Message;
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.configuration.ConfiguredBeanLocator;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.feature.AbstractFeature;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.service.Service;
import org.apache.cxf.service.model.DescriptionInfo;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.ws.policy.attachment.external.ExternalAttachmentProvider;
import org.apache.cxf.ws.policy.attachment.reference.ReferenceResolver;
import org.apache.cxf.ws.policy.attachment.reference.RemoteReferenceResolver;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyReference;
import org.apache.neethi.PolicyRegistry;


/**
 * Configures a Server, Client, Bus with the specified policies. If a series of 
 * Policy <code>Element</code>s are supplied, these will be loaded into a Policy
 * class using the <code>PolicyBuilder</code> extension on the bus. If the 
 * PolicyEngine has not been started, this feature will start it.
 *
 * @see PolicyBuilder
 * @see AbstractFeature
 */
@NoJSR250Annotations
public class WSPolicyFeature extends AbstractFeature {
    
    private static final ResourceBundle BUNDLE = BundleUtils.getBundle(WSPolicyFeature.class);
    
    private Collection<Policy> policies;
    private Collection<Element> policyElements;
    private Collection<Element> policyReferenceElements;
    private boolean ignoreUnknownAssertions;
    private AlternativeSelector alternativeSelector; 
    private boolean enabled = true;
  
       

    public WSPolicyFeature() {
        super();
    }

    public WSPolicyFeature(Policy... ps) {
        super();
        policies = new ArrayList<Policy>();
        Collections.addAll(policies, ps);
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
    
    @Override
    public void initialize(Bus bus) {
        
        // this should never be null as features are initialised only
        // after the bus and all its extensions have been created
        
        PolicyEngine pe = bus.getExtension(PolicyEngine.class);
        
        synchronized (pe) {
            pe.setEnabled(enabled);
            pe.setIgnoreUnknownAssertions(ignoreUnknownAssertions);
            if (null != alternativeSelector) {
                pe.setAlternativeSelector(alternativeSelector);
            }
        }
    }
    
    private MessageImpl createMessage(Endpoint e, Bus b) {
        MessageImpl m = new MessageImpl();
        Exchange ex = new ExchangeImpl();
        m.setExchange(ex);
        ex.put(Endpoint.class, e);
        ex.put(Bus.class, b);
        ex.put(Service.class, e.getService());
        return m;
    }

    @Override
    public void initialize(Client client, Bus bus) {
        Endpoint endpoint = client.getEndpoint();
        Policy p = initializeEndpointPolicy(endpoint, bus);
        PolicyEngine pe = bus.getExtension(PolicyEngine.class);
        EndpointInfo ei = endpoint.getEndpointInfo();
        EndpointPolicy ep = pe.getClientEndpointPolicy(ei, null, null);
        pe.setClientEndpointPolicy(ei, ep.updatePolicy(p, createMessage(endpoint, bus)));
    }

    @Override
    public void initialize(Server server, Bus bus) {
        Endpoint endpoint = server.getEndpoint();
        Policy p = initializeEndpointPolicy(endpoint, bus);
        PolicyEngine pe = bus.getExtension(PolicyEngine.class);
        EndpointInfo ei = endpoint.getEndpointInfo();
        EndpointPolicy ep = pe.getServerEndpointPolicy(ei, null, null);
        pe.setServerEndpointPolicy(ei, ep.updatePolicy(p, createMessage(endpoint, bus)));

        // Add policy to the service model (and consequently to the WSDL)
        // FIXME - ideally this should probably be moved up to where the policies are applied to the
        // endpoint, rather than this late.  As a consequence of its location, you have to declare a
        // ws policy feature on every endpoint in order to get any policy attachments into the
        // wsdl.  Alternatively add to the WSDLServiceBuilder somehow.
        ServiceModelPolicyUpdater pu = new ServiceModelPolicyUpdater(ei);
        for (PolicyProvider pp : ((PolicyEngineImpl) pe).getPolicyProviders()) {
            if (pp instanceof ExternalAttachmentProvider) {
                pu.addPolicyAttachments(((ExternalAttachmentProvider) pp).getAttachments());
            }
        }
    }

    private Policy initializeEndpointPolicy(Endpoint endpoint, Bus bus) {
        
        initialize(bus);
        DescriptionInfo i = endpoint.getEndpointInfo().getDescription();
        Collection<Policy> loadedPolicies = null;
        if (policyElements != null || policyReferenceElements != null) {
            loadedPolicies = new ArrayList<Policy>();
            PolicyBuilder builder = bus.getExtension(PolicyBuilder.class); 
            if (null != policyElements) {
                for (Element e : policyElements) {
                    loadedPolicies.add(builder.getPolicy(e));
                }
            }
            if (null != policyReferenceElements) {
                for (Element e : policyReferenceElements) {
                    PolicyReference pr = builder.getPolicyReference(e);
                    Policy resolved = resolveReference(pr, builder, bus, i);
                    if (null != resolved) {
                        loadedPolicies.add(resolved);
                    }
                }
            }
        } 
        
        Policy thePolicy = new Policy();
        
        if (policies != null) {
            for (Policy p : policies) {
                thePolicy = thePolicy.merge(p);
            }
        }
        
        if (loadedPolicies != null) {
            for (Policy p : loadedPolicies) {
                thePolicy = thePolicy.merge(p);
            }
        }
        
        return thePolicy;
    }
    
    public Collection<Policy> getPolicies() {
        if (policies == null) {
            policies = new ArrayList<Policy>();
        }
        return policies;
    }

    public void setPolicies(Collection<Policy> policies) {
        this.policies = policies;
    }

    public Collection<Element> getPolicyElements() {
        if (policyElements == null) {
            policyElements = new ArrayList<Element>();
        }
        return policyElements;
    }

    public void setPolicyElements(Collection<Element> elements) {
        policyElements = elements;
    }
    
    public Collection<Element> getPolicyReferenceElements() {
        if (policyReferenceElements == null) {
            policyReferenceElements = new ArrayList<Element>();
        }
        return policyReferenceElements;
    }

    public void setPolicyReferenceElements(Collection<Element> elements) {
        policyReferenceElements = elements;
    }
      
    public void setIgnoreUnknownAssertions(boolean ignore) {
        ignoreUnknownAssertions = ignore;
    } 
    
    
    public void setAlternativeSelector(AlternativeSelector as) {
        alternativeSelector = as;
    }
    
    Policy resolveReference(PolicyReference ref, PolicyBuilder builder, Bus bus, DescriptionInfo i) {
        Policy p = null;
        if (!ref.getURI().startsWith("#")) {
            String base = i == null ? null : i.getBaseURI();
            p = resolveExternal(ref, base, bus);
        } else {
            p = resolveLocal(ref, bus, i);
        }
        if (null == p) {
            throw new PolicyException(new Message("UNRESOLVED_POLICY_REFERENCE_EXC", BUNDLE, ref.getURI()));
        }
        
        return p;
    }   
    
    Policy resolveLocal(PolicyReference ref, final Bus bus, DescriptionInfo i) {
        String uri = ref.getURI().substring(1);
        String absoluteURI = i == null ? uri : i.getBaseURI() + uri;
        PolicyRegistry registry = bus.getExtension(PolicyEngine.class).getRegistry();
        Policy resolved = registry.lookup(absoluteURI);
        if (null != resolved) {
            return resolved;
        }
        ReferenceResolver resolver = new ReferenceResolver() {
            public Policy resolveReference(String uri) {
                PolicyBean pb = bus.getExtension(ConfiguredBeanLocator.class)
                        .getBeanOfType(uri, PolicyBean.class);
                if (null != pb) {
                    PolicyBuilder builder = bus.getExtension(PolicyBuilder.class);
                    return builder.getPolicy(pb.getElement()); 
                }
                return null;
            }
        };
        resolved = resolver.resolveReference(uri);
        if (null != resolved) {
            ref.setURI(absoluteURI);
            registry.register(absoluteURI, resolved);
        }
        return resolved;
    }
    
    protected Policy resolveExternal(PolicyReference ref,  String baseURI, Bus bus) {
        PolicyBuilder builder = bus.getExtension(PolicyBuilder.class);
        ReferenceResolver resolver = new RemoteReferenceResolver(baseURI, builder);
        PolicyRegistry registry = bus.getExtension(PolicyEngine.class).getRegistry();
        Policy resolved = registry.lookup(ref.getURI());
        if (null != resolved) {
            return resolved;
        }
        return resolver.resolveReference(ref.getURI());
    }
}
