/*
 * 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.
 *
 * $Header:$
 */
package org.apache.beehive.netui.pageflow.internal;

import org.apache.beehive.netui.core.urltemplates.URLTemplate;
import org.apache.beehive.netui.core.urltemplates.URLTemplates;
import org.apache.beehive.netui.core.urltemplates.URLTemplatesFactory;
import org.apache.beehive.netui.util.logging.Logger;
import org.apache.beehive.netui.util.xml.DomUtils;
import org.apache.beehive.netui.util.xml.XmlInputStreamResolver;

import java.io.InputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import javax.servlet.ServletContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * Methods for configuring and retrieving the URLTemplate object.
 */
public class DefaultURLTemplatesFactory extends URLTemplatesFactory
{
    private static final Logger _log = Logger.getInstance( DefaultURLTemplatesFactory.class );

    // Constants for schema elements
    private static final String KEY = "key";
    private static final String NAME = "name";
    private static final String TEMPLATE_NAME = "template-name";
    private static final String URL_TEMPLATE = "url-template";
    private static final String URL_TEMPLATE_REF = "url-template-ref";
    private static final String URL_TEMPLATE_REF_GROUP = "url-template-ref-group";
    private static final String VALUE = "value";

    private static final String CONFIG_SCHEMA = "org/apache/beehive/netui/core/urltemplates/schema/url-template-config.xsd";

    private static final XmlInputStreamResolver SCHEMA_RESOLVER = new XmlInputStreamResolver() {
        public String getResourcePath() {
            return CONFIG_SCHEMA;
        }

        public InputStream getInputStream() {
            return DefaultURLTemplatesFactory.class.getClassLoader().getResourceAsStream(getResourcePath());
        }
    };

    // The actual URL templates and template ref groups
    private URLTemplates _urlTemplates;

    /**
     * Returns an array of the URL templates.
     *
     * <p>
     * Always returns a copy of a URLTemplate with the same
     * parsed template data but its own cleared set of
     * token values for the substitue() methods.
     * This allows multiple client requests access to
     * the same parsed template structure, without requiring
     * it to be parsed for each request.
     * </p>
     *
     * @return the URL templates
     */
    public URLTemplate[] getURLTemplates()
    {
        if ( _urlTemplates == null )
        {
            return null;
        }

        return _urlTemplates.getTemplates();
    }

    /**
     * Returns URL template given the name of the template.
     *
     * @param name name of the template
     * @return template
     */
    public URLTemplate getURLTemplate( String name )
    {
        assert _urlTemplates != null : "The template config file has not been loaded.";

        if ( _urlTemplates == null )
        {
            return null;
        }

        return _urlTemplates.getTemplate( name );
    }

    /**
     * Returns URL template name of the given type (by key) from the
     * desired reference group.
     *
     * @param refGroupName name of a group of templates from the config file.
     * @param key          type of the template
     * @return template name
     */
    public String getTemplateNameByRef( String refGroupName, String key )
    {
        assert _urlTemplates != null : "The template config file has not been loaded.";

        if ( _urlTemplates == null )
        {
            return null;
        }

        String ref = _urlTemplates.getTemplateNameByRef( refGroupName, key );
        if ( ref == null )
        {
            // If the template is a secure template, look for the secure default
            // before resolving to the default
            if ( key.equals( URLTemplatesFactory.SECURE_RENDER_TEMPLATE ) ||
                    key.equals( URLTemplatesFactory.SECURE_ACTION_TEMPLATE ) ||
                    key.equals( URLTemplatesFactory.SECURE_RESOURCE_TEMPLATE ) )
            {
                ref = _urlTemplates.getTemplateNameByRef( refGroupName, URLTemplatesFactory.SECURE_DEFAULT_TEMPLATE );
            }
        }

        return ref;
    }

    /**
     * Initialization method that parses the URL template config file to
     * get the URL templates and template reference groups.
     *
     * @param servletContext the current ServletContext.
     */
    public void load( ServletContext servletContext )
    {
        _urlTemplates = new URLTemplates();
        InputStream xmlInputStream = null;
        InputStream xsdInputStream = null;

        try
        {
            xmlInputStream = servletContext.getResourceAsStream( _configFilePath );
            if ( xmlInputStream != null ) {
                /* load the XSD input stream */
                xsdInputStream = SCHEMA_RESOLVER.getInputStream();

                final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
                final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
                final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";

                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                dbf.setValidating(true);
                dbf.setNamespaceAware(true);
                dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
                dbf.setAttribute(JAXP_SCHEMA_SOURCE, xsdInputStream);

                DocumentBuilder db = dbf.newDocumentBuilder();

                /* add an ErrorHandler that just logs validation problems */
                db.setErrorHandler(new ErrorHandler() {
                    public void warning(SAXParseException exception) {
                        _log.info("Validation warning validating config file \"" + _configFilePath + "\" against XML Schema \"" + SCHEMA_RESOLVER.getResourcePath());
                    }

                    public void error(SAXParseException exception) {
                        _log.error("Validation errors occurred parsing the config file \"" + _configFilePath + "\".  Cause: " + exception, exception);
                    }

                    public void fatalError(SAXParseException exception) {
                        _log.error("Validation errors occurred parsing the config file \"" + _configFilePath + "\".  Cause: " + exception, exception);
                    }
                });

                db.setEntityResolver(new EntityResolver() {
                    public InputSource resolveEntity(String publicId, String systemId) {
                        if(systemId.endsWith("/url-template-config.xsd")) {
                            InputStream inputStream = DefaultURLTemplatesFactory.class.getClassLoader().getResourceAsStream(CONFIG_SCHEMA);
                            return new InputSource(inputStream);
                        }
                        else return null;
                    }
                });

                Document document = db.parse(xmlInputStream);
                Element root = document.getDocumentElement();
                loadTemplates( root );
                loadTemplateRefGroups( root );
       }
       else
       {
           if ( _log.isInfoEnabled() )
               _log.info( "Running without URL template descriptor, " + _configFilePath );
       }
   }
   catch ( ParserConfigurationException pce )
   {
       _log.error( "Problem loading URL template descriptor file " + _configFilePath, pce );
   }
   catch ( SAXException se )
   {
       _log.error( "Problem parsing URL template descriptor in " + _configFilePath, se );
   }
   catch ( IOException ioe )
   {
       _log.error( "Problem reading URL template descriptor file " + _configFilePath, ioe );
   }
   finally
   {
       // Close the streams
       try { if ( xmlInputStream != null ) xmlInputStream.close(); } catch ( Exception ignore ) {}
       try { if ( xsdInputStream != null ) xsdInputStream.close(); } catch( IOException ignore ) {}
   }
}

/**
* Loads the templates from a URL template config document.
*
* @param parent
*/
    private void loadTemplates( Element parent )
    {
        // Load templates
        List templates = DomUtils.getChildElementsByName( parent, URL_TEMPLATE );
        for ( int i = 0; i < templates.size(); i++ )
        {
            Element template = ( Element ) templates.get( i );
            String name = getElementText( template, NAME );
            if ( name == null )
            {
                _log.error( "Malformed URL template descriptor in " + _configFilePath
                            + ". The url-template name is missing." );
                continue;
            }

            String value = getElementText( template, VALUE );
            if ( value == null )
            {
                _log.error( "Malformed URL template descriptor in " + _configFilePath
                            + ". The url-template value is missing for template " + name );
                continue;
            }

            if ( _log.isDebugEnabled() )
            {
                _log.debug( "[URLTemplate] " + name + " = " + value );
            }

            URLTemplate urlTemplate = new URLTemplate( value, name );
            if ( urlTemplate.verify( _knownTokens, _requiredTokens ) )
            {
                _urlTemplates.addTemplate( name, urlTemplate );
            }
        }
    }

    /**
     * Loads the template reference groups from a URL template config document.
     *
     * @param parent
     */
    private void loadTemplateRefGroups( Element parent )
    {
        // Load template refs
        List templateRefGroups = DomUtils.getChildElementsByName( parent, URL_TEMPLATE_REF_GROUP );;
        for ( int i = 0; i < templateRefGroups.size(); i++ )
        {
            Element refGroupElement = ( Element ) templateRefGroups.get( i );
            String refGroupName = getElementText( refGroupElement, NAME );
            if ( refGroupName == null )
            {
                _log.error( "Malformed URL template descriptor in " + _configFilePath
                            + ". The url-template-ref-group name is missing." );
                continue;
            }

            HashMap refGroup = new HashMap();
            List templateRefs = DomUtils.getChildElementsByName( refGroupElement, URL_TEMPLATE_REF );;
            for ( int j = 0; j < templateRefs.size(); j++ )
            {
                Element templateRefElement = ( Element ) templateRefs.get( j );
                String key = getElementText( templateRefElement, KEY );
                if ( key == null )
                {
                    _log.error( "Malformed URL template descriptor in " + _configFilePath
                                + ". The url-template-ref key is missing in url-template-ref-group " + refGroupName );
                    continue;
                }

                String name = getElementText( templateRefElement, TEMPLATE_NAME );
                if ( name != null )
                {
                    refGroup.put( key, name );
                    if ( _log.isDebugEnabled() )
                    {
                        _log.debug( "[" + refGroupName + " URLTemplate] " + key + " = " + name );
                    }
                }
                else
                {
                    _log.error( "Malformed URL template descriptor in " + _configFilePath
                                + ". The url-template-ref template-name is missing in url-template-ref-group " + refGroupName );
                }
            }

            if ( refGroup.size() != 0 )
            {
                _urlTemplates.addTemplateRefGroup( refGroupName, refGroup );
            }
        }
    }

    private String getElementText( Element parent, String elementName )
    {
        Element child = DomUtils.getChildElementByName( parent, elementName );
        if ( child != null )
        {
            String text = DomUtils.getElementText( child );
            if ( text != null )
            {
                text = text.trim();
                return text.length() == 0 ? null : text;
            }
        }

        return null;
    }
}
