/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

package com.sun.enterprise.web.reconfig;

import java.util.ArrayList;
import com.sun.enterprise.admin.event.AdminEventListenerException;
import com.sun.enterprise.admin.event.http.HSAccessLogEvent;
import com.sun.enterprise.admin.event.http.HSAccessLogEventListener;
import com.sun.enterprise.admin.event.http.HSVirtualServerEvent;
import com.sun.enterprise.admin.event.http.HSVirtualServerEventListener;
import com.sun.enterprise.admin.event.http.HSServiceEvent;
import com.sun.enterprise.admin.event.http.HSServiceEventListener;
import com.sun.enterprise.admin.event.http.HSHttpFileCacheEvent;
import com.sun.enterprise.admin.event.http.HSHttpFileCacheEventListener;
import com.sun.enterprise.admin.event.http.HSHttpListenerEvent;
import com.sun.enterprise.admin.event.http.HSHttpListenerEventListener;
import com.sun.enterprise.admin.event.http.HSHttpProtocolEvent;
import com.sun.enterprise.admin.event.http.HSHttpProtocolEventListener;
import com.sun.enterprise.config.ConfigAdd;
import com.sun.enterprise.config.ConfigBean;
import com.sun.enterprise.config.ConfigChange;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.config.serverbeans.ElementProperty;
import com.sun.enterprise.config.serverbeans.HttpListener;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.ServerBeansFactory;
import com.sun.enterprise.config.serverbeans.VirtualServer;
import com.sun.enterprise.web.PEWebContainer;

/**
 * Class responsible for handling dynamic reconfiguration events for
 * various elements in domain.xml.
 *
 * @author Jean-Francois Arcand
 * @author Jan Luehe
 */ 
public class ReconfigListener
    implements HSAccessLogEventListener,
               HSHttpListenerEventListener,
               HSServiceEventListener,
               HSVirtualServerEventListener,
               HSHttpProtocolEventListener,
               HSHttpFileCacheEventListener {
   
    private PEWebContainer webContainer;

    public ReconfigListener(PEWebContainer webContainer) {
        this.webContainer = webContainer;
    }


    /*
     * Dynamic reconfig events pertaining to <access-log>
     */
    
    public void handleDelete(HSAccessLogEvent event)
            throws AdminEventListenerException {
        // Ignore
    }
    
    public void handleUpdate(HSAccessLogEvent event)
            throws AdminEventListenerException {

        if (webContainer == null || event == null) {
            return;
        }
           
        try {
                   
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            if (config == null) {
                return;
            }
            
            webContainer.updateAccessLog(config.getHttpService());

        } catch (Exception e) {
           throw new AdminEventListenerException(e);
        } 
    }

    public void handleCreate(HSAccessLogEvent event)
            throws AdminEventListenerException {
        // Ignore
    }


    /*
     * Dynamic reconfig events pertaining to <http-listener>
     */

    /**
     * Create a new http-listener.
     */
    public void handleCreate(HSHttpListenerEvent event)
            throws AdminEventListenerException {
        
        if (webContainer == null || event == null) {
            return;       
        }

        try {             
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            
            if ( config == null) return;
            
            ConfigAdd configAdd = null;
            ArrayList configChangeList = event.getConfigChangeList();
            HttpListener httpBean = null;
            String xpath = null;
            Object object;
            Object configObject;
            
            for (int i=0; i < configChangeList.size(); i++){
                configObject = configChangeList.get(i);

                if ( configObject instanceof ConfigAdd) {
                    configAdd = (ConfigAdd)configObject;
                    xpath = configAdd.getXPath();
                    if( xpath != null){
                        object = configContext.exactLookup(xpath);
                        if ( object instanceof HttpListener){
                            httpBean = (HttpListener)object;
                            webContainer.createConnector(httpBean, 
                                                         config.getHttpService());
                        }
                    }
                }
            }
        } catch (Exception ex) {
           throw new AdminEventListenerException(ex);
        }  
    }
    
    /**
     * Update an existing http-listener.
     */
    public void handleUpdate(HSHttpListenerEvent event)
            throws AdminEventListenerException {   
        
        if (webContainer == null || event == null) {
            return;
        }
                                     
        try {                        
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            
            if ( config == null) return;
            
            ConfigChange configChange = null;
            ArrayList<ConfigChange> configChangeList =
                event.getConfigChangeList();
            HttpListener httpBean = null;
            String xpath = null;
            String parentXpath = null;
            Object object;
            ElementProperty elementProperty = null;

            for (int i=0; i < configChangeList.size(); i++){
                configChange = configChangeList.get(i);
                xpath = configChange.getXPath();
                if (xpath != null) {
                    object = configContext.exactLookup(xpath);
                    if (object != null) {
                        if (object instanceof HttpListener) {
                            webContainer.updateConnector(
                                (HttpListener)object, 
                                config.getHttpService());
                        } else if (object instanceof ElementProperty) {
                            // Property has been added or updated
                            parentXpath = configChange.getParentXPath();
                            if (parentXpath == null) {
                                parentXpath =
                                    xpath.substring(0, xpath.lastIndexOf("/"));
                            }
                            httpBean = (HttpListener)
                                configContext.exactLookup(parentXpath);
                            webContainer.updateConnector(
                                httpBean, 
                                config.getHttpService());
                        }
                    } else {
                        // Property has been deleted
                        parentXpath =
                            xpath.substring(0, xpath.lastIndexOf("/"));
                        httpBean = (HttpListener)
                            configContext.exactLookup(parentXpath);
                        webContainer.updateConnector(
                            httpBean, 
                            config.getHttpService());
                    }
                }
            }
        } catch (Exception ex) {
           throw new AdminEventListenerException(ex);
        }
    }
    
    /**
     * Delete an existing http-listener. 
     *
     * Implementation note: we cannot use the same approach used when
     * handling create/update event since the element is deleted before this
     * method is invoked (strange behaviour). Instead we need to find by 
     * ourself which http-listener has been deleted.
     */
    public void handleDelete(HSHttpListenerEvent event) 
            throws AdminEventListenerException {
         
        if (webContainer == null || event == null) {
            return;       
        }

        try {
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
                
            if ( config == null) return;
                
            webContainer.deleteConnector(config.getHttpService());

        }  catch( Exception ex){
           throw new AdminEventListenerException(ex);
        }      
    }     


    /*
     * Dynamic reconfig events pertaining to <http-service>
     */

    public void handleDelete(HSServiceEvent event)
            throws AdminEventListenerException {
        // Ignore
    }

    public void handleUpdate(HSServiceEvent event)
            throws AdminEventListenerException {

        if (webContainer == null || event == null) {
            return;
        }

        try {
                   
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            if (config == null) {
                return;
            }
            
            webContainer.updateHttpService(config.getHttpService());

        } catch (Exception e) {
           throw new AdminEventListenerException(e);
        } 
    }

    public void handleCreate(HSServiceEvent event)
            throws AdminEventListenerException {
        // Ignore
    }


    /*
     * Dynamic reconfig events pertaining to <virtual-server>
     */

    /**
     * Create a new virtual-server.
     */
    public void handleCreate(HSVirtualServerEvent event)
            throws AdminEventListenerException {
        
        if (webContainer == null || event == null) {
            return;
        }
           
        try {                                                          
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
                                    
            if ( config == null) return;
            
            ConfigAdd configAdd = null;
            ArrayList configChangeList = event.getConfigChangeList();
            VirtualServer vsBean = null;
            String xpath = null;
            Server serverBean = ServerBeansFactory.getServerBean(configContext);
            ElementProperty elementProperty = null;
            Object object;
            Object configObject;
            
            for (int i=0; i < configChangeList.size(); i++){
                configObject = configChangeList.get(i);

                if ( configObject instanceof ConfigAdd) {
                    configAdd = (ConfigAdd)configObject;
                    xpath = configAdd.getXPath();
                    if( xpath != null){
                        object = configContext.exactLookup(xpath);

                        if ( object instanceof VirtualServer){
                            vsBean = (VirtualServer)object;
                            webContainer.createHost(vsBean,configContext,true);
                        } else if (object instanceof ElementProperty) {
                            xpath = xpath.substring(0,xpath.lastIndexOf("/"));
                            vsBean = (VirtualServer)configContext.exactLookup(xpath);                       
                            elementProperty = (ElementProperty)object;
                            webContainer.updateHostProperties(
                                       vsBean, 
                                       elementProperty.getName(),
                                       elementProperty.getValue(),
                                       config.getHttpService(),
                                       config.getSecurityService());
                        }
                    }
                }
            }
        } catch (Exception ex) {
           throw new AdminEventListenerException(ex);
        } 
    }
    
    /**
     * Update an existing virtual-server.
     */
    public void handleUpdate(HSVirtualServerEvent event)
            throws AdminEventListenerException {  
        
        if (webContainer == null || event == null) {
            return;
        }
           
        try {                                            
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            
            if ( config == null) return;
            
            ConfigChange configChange = null;
            ArrayList<ConfigChange> configChangeList = 
                event.getConfigChangeList();
            VirtualServer vsBean = null;
            String xpath = null;
            String parentXpath = null;
            Server serverBean = ServerBeansFactory.getServerBean(configContext);
            ElementProperty elementProperty = null;
            Object object;           

            for (int i=0; i < configChangeList.size(); i++){
                configChange = configChangeList.get(i);
                
                xpath = configChange.getXPath();
                if (xpath != null) {
                    object = configContext.exactLookup(xpath);
                    if (object != null) {
                        if ( object instanceof VirtualServer){
                            webContainer.updateHost((VirtualServer)object, 
                                                    config.getHttpService(), 
                                                    serverBean);
                        } else if (object instanceof ElementProperty) {
                            // Property has been added or updated
                            parentXpath = configChange.getParentXPath();
                            if (parentXpath == null) {
                                parentXpath =
                                    xpath.substring(0, xpath.lastIndexOf("/"));
                            }
                            vsBean = (VirtualServer)
                                configContext.exactLookup(parentXpath);
                            elementProperty = (ElementProperty)object;
                            checkProperty(elementProperty.getName());
                            webContainer.updateHostProperties(
                                       vsBean, 
                                       elementProperty.getName(),
                                       elementProperty.getValue(),
                                       config.getHttpService(),
                                       config.getSecurityService());
                        }

                    } else {
                        // Property has been deleted.
                        // Determine the property's name
                        int lastSlash = xpath.lastIndexOf("/");
                        parentXpath = xpath.substring(0, lastSlash);
                        String propName = xpath.substring(lastSlash);
                        if (propName != null) {
                            // e.g., /element-property[@name='sso-enabled']
                            int beginQuote = propName.indexOf("'");
                            int endQuote = propName.lastIndexOf("'");
                            if (endQuote > beginQuote) {
                                propName = propName.substring(beginQuote+1,
                                                              endQuote);
                                checkProperty(propName);
                                vsBean = (VirtualServer)
                                    configContext.exactLookup(parentXpath);
                                webContainer.updateHostProperties(
                                       vsBean,
                                       propName,
                                       null,
                                       config.getHttpService(),
                                       config.getSecurityService());
                            }
                        }
                    }
                }
            }
        } catch (Exception ex) {
           throw new AdminEventListenerException(ex);
        } 
    }
    
    /**
     * Delete an existing virtual-server. 
     *
     * Implementation note: we cannot use the same approach used when
     * handling create/update event since the element is deleted before this
     * method is invoked (strange behaviour). Instead we need to find by 
     * ourself which virtual-server has been deleted.
     */
    public void handleDelete(HSVirtualServerEvent event)
            throws AdminEventListenerException {

        if (webContainer == null || event == null) {
            return;       
        }

        try {
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            if (config != null) {
                webContainer.deleteHost(config.getHttpService());    
            }
        } catch( Exception ex){
           throw new AdminEventListenerException(ex);
        }    
    }


    /*
     * Dynamic reconfig events pertaining to <http-protocol>
     */

    public void handleDelete(HSHttpProtocolEvent event)
            throws AdminEventListenerException {
        // Ignore
    }

    public void handleUpdate(HSHttpProtocolEvent event)
            throws AdminEventListenerException {

        if (webContainer == null) {
            return;
        }

        try {
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            if (config == null) {
                return;
            }
            webContainer.updateHttpService(config.getHttpService());
        } catch (Exception e) {
           throw new AdminEventListenerException(e);
        } 
    }

    public void handleCreate(HSHttpProtocolEvent event)
            throws AdminEventListenerException {
        // Ignore
    }


    /*
     * Dynamic reconfig events pertaining to <http-file-cache>
     */

    public void handleDelete(HSHttpFileCacheEvent event)
            throws AdminEventListenerException {
        // Ignore
    }

    public void handleUpdate(HSHttpFileCacheEvent event)
            throws AdminEventListenerException {

        if (webContainer == null) {
            return;
        }

        try {
            ConfigContext configContext = event.getConfigContext();
            Config config = ServerBeansFactory.getConfigBean(configContext);
            if (config == null) {
                return;
            }
            webContainer.updateHttpService(config.getHttpService());
        } catch (Exception e) {
           throw new AdminEventListenerException(e);
        } 
    }

    public void handleCreate(HSHttpFileCacheEvent event)
            throws AdminEventListenerException {
        // Ignore
    }


    /*
     * Private methods
     */

    /**
     * Checks if the given property is dynamically reconfigurable.
     *
     * @param propName The property name
     *
     * @exception AdminEventListenerException if the given property is not
     * dynamically reconfigurable
     */
    private void checkProperty(String propName)
            throws AdminEventListenerException {

        if (propName != null && propName.startsWith("alternatedocroot")) {
            throw new AdminEventListenerException(
                "Property " + propName + " not dynamically reconfigurable. "
                + "Server restart required.");
        }
    }
}
