/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, 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/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.enterprise.config.serverbeans.validation;

//jdk imports
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.File;
import java.util.StringTokenizer;
import java.util.HashMap;
import org.xml.sax.SAXException;
import org.xml.sax.InputSource;
import java.io.IOException;
import java.util.Vector;
import java.util.StringTokenizer;
import java.lang.Class;
import java.lang.reflect.Constructor;
import java.net.URL;

// Logging
import java.util.logging.Logger;
import java.util.logging.Level;
import com.sun.logging.LogDomains;

import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigContextEvent;
import com.sun.enterprise.config.ConfigContextEventListener;
import com.sun.enterprise.admin.AdminValidationException;
import com.sun.enterprise.admin.pluggable.ValidationExtensionFeature;
import com.sun.enterprise.admin.server.core.AdminService;

// config imports
import com.sun.enterprise.config.ConfigFactory;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.ConfigBean;
import com.sun.enterprise.config.serverbeans.*;
import com.sun.enterprise.admin.meta.MBeanRegistryFactory;
import com.sun.enterprise.admin.meta.MBeanRegistry;

/**
 *  Class which loads all the validator descriptor information from a xml file into a hash map. 
 *  Validator uses this Hash Map and invokes the particular test case depending on xml tag

    @author Srinivas Krishnan
    @version 2.0
*/

public class DomainMgr implements ConfigContextEventListener {
    
    // Logging
    static Logger _logger = LogDomains.getLogger(LogDomains.APPVERIFY_LOGGER);
    
    LocalStringManagerImpl smh = StringManagerHelper.getLocalStringsManager();
    HashMap tests = new HashMap();
    transient private long lastModified = 0; 
    public NameListMgr _nameListMgr;
    public MBeanRegistry _mbeanRegistry;
    public DomainMgr() {
        this(MBeanRegistryFactory.getAdminContext().getAdminConfigContext(), false);
    }

    public DomainMgr(ConfigContext ctx, boolean bStaticContext) {
        this(ctx, bStaticContext, null);
    }
   
    public DomainMgr(ConfigContext ctx, boolean bStaticContext, MBeanRegistry registry) {
        String validationDescrFile = 
                "com/sun/enterprise/config/serverbeans/validation/config/ServerTestList.xml";
        loadDescriptors(validationDescrFile);
        String extValidationDescrFile = null;
        // Add extra validation descriptors from extended Feature
        ValidationExtensionFeature vef = AdminService.getValidationExtensionFeature();
        if (vef != null) {
            extValidationDescrFile = vef.getValidationDescriptorFile();
            if (extValidationDescrFile != null)
                loadDescriptors(extValidationDescrFile);
        }
        
        _nameListMgr = new NameListMgr(ctx, bStaticContext);
        if(registry==null)
            _mbeanRegistry = MBeanRegistryFactory.getAdminMBeanRegistry();
        else
            _mbeanRegistry = registry;
    }
    
    MBeanRegistry getMBeanRegistry()
    {
        return _mbeanRegistry;
    }
    // Get the path of validation descriptor xml file from System property class path
    private String getTestFile(String file) throws Exception {
        URL propertiesFile = DomainMgr.class.getClassLoader().getResource(file);
//TODO: for test only
//  URL propertiesFile = (new File("/ias/admin/validator/descr.xml")).toURL();
        return propertiesFile.toString();
    }

    // Loads all validation descriptors from XML file into the Hash Map
    public boolean loadDescriptors(String file) {
        boolean allIsWell = true;

        try {
            //tests.clear();
            DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            InputSource is = new InputSource(getTestFile(file));
            Document doc = db.parse(is);
            NodeList list = doc.getElementsByTagName("element");
            for (int i=0;i<list.getLength();i++) {
                Element e = (Element) list.item(i);
                String elementName  =  e.getAttribute("name");
                String elementXPath =  e.getAttribute("xpath");
                String elementCustomClass = e.getAttribute("custom-class") ;
                String testName = e.getAttribute("test-name");
                if(testName==null || testName.length()==0)
                {
                    testName = XPathHelper.convertName(elementName);
                }
                if (null == elementCustomClass || elementCustomClass.length() == 0){
                    elementCustomClass = testName;
                }
                String[] required_children = null;
                String[] exclusive_list    = null;
                String elemList = e.getAttribute("required-children");
                if(elemList!=null && elemList.length()>0)
                {
                    required_children = elemList.split(",");
                }
                elemList = e.getAttribute("exclusive-list");
                if(elemList!=null && elemList.length()>0)
                {
                    exclusive_list = elemList.split(",");
                }
                String key = e.getAttribute("key");
                if(key!=null && key.length()==0)
                    key = null;
                Vector attributes = new Vector();
                NodeList nl = e.getChildNodes();
                for (int index=0,j=0;j<nl.getLength();j++) {
                    String temp;
                    String nodeName = nl.item(j).getNodeName().trim();
                    String nameValue=null;
                    String typeValue=null;
                    NamedNodeMap nodeMap=null;
                    AttrType attr=null;
                   
                    Node n = nl.item(j);
                    
                    if("attribute".equals(nodeName) || "optional-attribute".equals(nodeName)) {
                        nodeMap = n.getAttributes();

                        nameValue   = getAttr(nodeMap, "name");
                        typeValue   = getAttr(nodeMap, "type");
                        if("string".equals(typeValue)) 
                        {
                            attr = new AttrString(nameValue, typeValue, "optional-attribute".equals(nodeName) );
                            if((temp=getAttr(nodeMap, "max-length"))!=null)
                               ((AttrString)attr).setMaxLength(Integer.parseInt(temp));
                            if((temp=getAttr(nodeMap, "enumeration"))!=null)
                            {
                                Vector ee = new Vector();
                                String[] strs = temp.split(",");
                                for(int k=0; k<strs.length; k++)
                                    ee.add(strs[k]);
                                ((AttrString)attr).setEnumstring(ee);
                            }
                            ((AttrString)attr).setRegExpression(getAttr(nodeMap, "regex"));
                        }
                        else if("file".equals(typeValue)) 
                        {
                            attr = new AttrFile(nameValue, typeValue, "optional-attribute".equals(nodeName));
                            if("true".equals(getAttr(nodeMap, "exists")))
                                ((AttrFile)attr).setCheckExists(true);
                        }
                        else if("integer".equals(typeValue))
                        {
                            attr = new AttrInt(nameValue,typeValue, "optional-attribute".equals(nodeName));
                            if((temp = getAttr(nodeMap, "range")) != null) 
                            {
                                String[] strs = temp.split(",");
                                if(!strs[0].equals("NA"))
                                    ((AttrInt)attr).setLowRange(Integer.parseInt(strs[0]));
                                if(!strs[1].equals("NA"))
                                    ((AttrInt)attr).setHighRange(Integer.parseInt(strs[1]));
                            }
                        }
                        else if("classname".equals(typeValue))
                            attr = new AttrClassName(nameValue, typeValue, "optional-attribute".equals(nodeName));
                        else if("address".equals(typeValue)) 
                            attr = new AttrAddress(nameValue,typeValue, "optional-attribute".equals(nodeName));
                        else if("jndi-unique".equals(typeValue)) 
                            attr = new AttrUniqueJNDI(nameValue,typeValue, "optional-attribute".equals(nodeName));

                        if(attr != null) 
                        {
                            attr.addRuleValue("belongs-to",     getAttrAsList(nodeMap, "belongs-to"));
                            attr.addRuleValue("references-to",  getAttrAsList(nodeMap, "references-to"));
                            attr.addRuleValue("le-than", getAttr(nodeMap, "le-than"));
                            attr.addRuleValue("ls-than", getAttr(nodeMap, "ls-than"));
                            attr.addRuleValue("ge-than", getAttr(nodeMap, "ge-than"));
                            attr.addRuleValue("gt-than", getAttr(nodeMap, "gt-than"));

                            attributes.add(index++,attr);
                        }
                    }
                }
                final ValidationDescriptor desc =
                       new ValidationDescriptor(this, elementName, 
                            elementXPath, elementCustomClass, 
                            key, attributes, required_children, exclusive_list);
                final GenericValidator validator= getGenericValidator(desc);
                if(validator != null) 
                    tests.put(testName, validator);
            }
        } catch (ParserConfigurationException e) {
            _logger.log(Level.WARNING, "parser_error", e);
            allIsWell = false;
        } catch (SAXException e) {
            _logger.log(Level.WARNING, "sax_error", e);
            allIsWell = false;     
        } catch (IOException e) {
            _logger.log(Level.WARNING, "error_loading_xmlfile", e);
            allIsWell = false;
        } catch(Exception e) {
            _logger.log(Level.WARNING, "error", e);
            allIsWell = false;
        }
        return allIsWell;
    }
    
    private String getAttr(NamedNodeMap nodeMap, String attrName)
    {
        Node node = nodeMap.getNamedItem(attrName);
        if(node != null)
            return node.getNodeValue();
        return null;
    }
    
    private String[] getAttrAsList(NamedNodeMap nodeMap, String attrName)
    {
        String attrValue = getAttr(nodeMap, attrName);
        if(attrValue==null)
            return null;
        return attrValue.split(",");
    }
        /**
         * Get the generic validator to be used for the given
         * validation descriptor
         * @param v the validation descriptor
         * @return a GenericValidator instance that is to be used to
         * validate elements which match the ValidationDescription
         */
    GenericValidator getGenericValidator(final ValidationDescriptor v) throws InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException{
        Class c = getValidatorClass(v.getCustomValidatorClass(), TEST_PACKAGE);
        String cn = c.getSimpleName();
        if(cn.equals("GenericValidator"))
        {
            String extTestPackage = null;
            ValidationExtensionFeature vef = AdminService.getValidationExtensionFeature();
            if (vef != null) {
                extTestPackage = vef.getTestPackage();
                if (extTestPackage != null) {
                    c = getValidatorClass(v.getCustomValidatorClass(), extTestPackage);
                }
            }
        }
        try {
            final Constructor con = c.getConstructor(new Class[]{ValidationDescriptor.class});
            return (GenericValidator) con.newInstance(new Object[]{v});
        }
        catch (NoSuchMethodException e){
            return null;
        }
        
    }
    
        /**
         * Find a class that will perform validation for the given
         * class name. The class returned is the first that can be
         * loaded from the following ordered list:
         * <ol>
         * <li>The class as specified by the className param</li>
         * <li>The class as specified by the className param,
         * prepended with the TEST_PACKAGE package name. </li>
         * <li>The GenericValidator class</li>
         * Setting the log level to CONFIG will provide logging
         * information as to which class was actually found and loaded.
         * @param className the name of the class for which a
         * validator class is to be found
         * @return the class which is to be used for validation
         **/
    Class getValidatorClass(final String className, String testPackage){
        Class c;
        final String cn = testPackage+className+"Test";
            try {
            c= Class.forName(cn);
        }
        catch (ClassNotFoundException cnfe2){
            c = GenericValidator.class;
        }
        _logger.log(Level.CONFIG, "validator using class \""+c.getName()+"\" to validate \""+cn+"\"");
        return c;
    }
    
    // Method invokes the validation function of the test case
    public Result check(ConfigContextEvent cce) {
        String name = cce.getName();
        String beanName = cce.getBeanName();
        Result result = null;


        if(name == null && beanName == null)
                return result;
        
        DomainCheck validator = (DomainCheck) tests.get(name);
        if(validator == null && beanName != null)
            validator = (DomainCheck) tests.get(beanName);
        try {
            if(validator != null)
                result = validator.validate(cce);
        } catch(Exception e) {
//System.out.println("+++++name="+name + " xpath=" + ((ConfigBean)cce.getObject()).getXPath());
            _logger.log(Level.WARNING, "domainxmlverifier.error_on_validation", e);
        }
        return result;
    }
    
    public void postAccessNotification(ConfigContextEvent ccce) {
    }
    
    public void postChangeNotification(ConfigContextEvent ccce)  {
    }
    
    public void preAccessNotification(ConfigContextEvent ccce) {
    }
    
    // Function invoked by the Config Bean for validation before writing into domain.xml
    // Registered as listeners with ConfigContext while creation
    public void preChangeNotification(ConfigContextEvent cce) {
        Result result = null;
        try{
           result = check(cce);
        } catch(Throwable t)
        {
            _logger.log(Level.WARNING, "Exception during validation ", t);
        }
        
        if(result != null && result.getStatus() == Result.FAILED)
        {
            _logger.log(Level.WARNING, "Validation error: " + result.getErrorDetails().toString());
            throw new AdminValidationException(result.getErrorDetailsAsString());
        }
    }
    ValidationDescriptor findValidationDescriptor(String beanName)
    {
        if(beanName!=null)
        {
            GenericValidator genVal = (GenericValidator)tests.get(beanName);
            if(genVal!=null)
                return genVal.desc;
        }
        return null;
    }

    GenericValidator findConfigBeanValidator(ConfigBean configBean)
    {
        
        if(configBean!=null)
        {
            String className = configBean.getClass().getName();
            int iLastDot = className.lastIndexOf('.');
            if(iLastDot>0)
               return (GenericValidator)tests.get(className.substring(iLastDot+1));
        }
        return null;
    }
    
    public static final String TEST_PACKAGE="com.sun.enterprise.config.serverbeans.validation.tests.";
}
