/*
 * $Header: /home/cvspublic/jakarta-slide/src/webdav/server/org/apache/slide/webdav/util/resourcekind/ResourceKindManager.java,v 1.8 2005/02/02 17:17:40 luetzkendorf Exp $
 * $Revision: 1.8 $
 * $Date: 2005/02/02 17:17:40 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation 
 *
 * Licensed 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.slide.webdav.util.resourcekind;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.slide.common.Domain;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.NamespaceConfig;
import org.apache.slide.common.PropertyName;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.util.conf.Configurable;
import org.apache.slide.util.conf.Configuration;
import org.apache.slide.util.conf.ConfigurationElement;
import org.apache.slide.util.conf.ConfigurationException;
import org.apache.slide.util.conf.Populate;
import org.apache.slide.util.logger.Logger;
import org.apache.slide.webdav.util.DeltavConstants;
import org.apache.slide.webdav.util.UriHandler;
import org.apache.slide.webdav.util.WebdavConstants;
import org.apache.slide.webdav.util.WebdavFeatures;
import org.apache.slide.webdav.util.properties.ConfigurablePropertyComputer;
import org.apache.slide.webdav.util.properties.ConfigurablePropertyValueValidator;
import org.apache.slide.webdav.util.properties.PropertyComputer;
import org.apache.slide.webdav.util.properties.PropertyDefaultProvider;
import org.apache.slide.webdav.util.properties.PropertyValueValidator;
import org.apache.slide.webdav.util.properties.SimpleDefaultProvider;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Central manager of resource kinds and there associated capabilities (like 
 * properties, methods, reports).
 */
public class ResourceKindManager
{
    private static String LOG_CHANNEL = ResourceKindConfigurator.class.getName();
    
    public static Kind ACTIVITY = new Kind("Activity", ActivityImpl.getInstance());
    public static Kind BASELINE = new Kind("Baseline", BaselineImpl.getInstance());
    public static Kind CHECKED_IN_VERSION_CONTROLLED = new Kind("CheckedInVersionControlled", CheckedInVersionControlledImpl.getInstance());
    public static Kind CHECKED_OUT = new Kind("CheckedOut", CheckedOutImpl.getInstance());
    public static Kind CHECKED_OUT_VERSION_CONTROLLED_CONFIG = new Kind("CheckedOutVersionControlledConfiguration", CheckedOutVersionControlledConfigurationImpl.getInstance());
    public static Kind CHECKED_OUT_VERSION_CONTROLLED = new Kind("CheckedOutVersionControlled", CheckedOutVersionControlledImpl.getInstance());
    public static Kind COLLECTION_VERSION = new Kind("CollectionVersion", CollectionVersionImpl.getInstance());
    public static Kind DELTAV_COMPLIANT = new Kind("DeltavCompliant", DeltavCompliantImpl.getInstance());
    public static Kind DELTAV_COMPLIANT_COLL = new Kind("DeltavCompliantCollection", DeltavCompliantCollectionImpl.getInstance());
    public static Kind DELTAV_COMPLIANT_UNMAPPED_URL = new Kind("DeltavCompliantUnmappedUrl", DeltavCompliantUnmappedUrlImpl.getInstance());
    public static Kind VERSIONABLE = new Kind("Versionable", VersionableImpl.getInstance());
    public static Kind VERSION = new Kind("Version", VersionImpl.getInstance());
    public static Kind VERSION_CONTROLLED = new Kind("VersionControlled", VersionControlledImpl.getInstance());
    public static Kind VERSION_CONTROLLED_COLL = new Kind("VersionControlledCollection", VersionControlledCollectionImpl.getInstance());
    public static Kind VERSION_CONTROLLED_CONFIG = new Kind("VersionControlledConfiguration", VersionControlledConfigurationImpl.getInstance());
    public static Kind VERSION_HISTORY = new Kind("VersionHistory", VersionHistoryImpl.getInstance());
    public static Kind WORKING = new Kind("Working", WorkingImpl.getInstance());
    public static Kind WORKSPACE = new Kind("Workspace", WorkspaceImpl.getInstance());
    public static Kind PRINCIPAL = new Kind("Principal", PrincipalImpl.getInstance());
    
    static {
        initFromDefaultConfig();
    }
    
    static public ResourceKind determineResourceKind(NamespaceAccessToken nsaToken, 
            NodeRevisionDescriptors nrds, NodeRevisionDescriptor nrd) 
    {
        UriHandler uh = UriHandler.getUriHandler( nrds, nrd );
        return determineResourceKind(nsaToken, uh.toString(), nrd);
    }
    
    static public ResourceKind determineResourceKind(NamespaceAccessToken nsaToken, 
            String resourcePath, NodeRevisionDescriptor nrd) 
    {
        UriHandler uh = UriHandler.getUriHandler(resourcePath);
        NamespaceConfig config = nsaToken.getNamespaceConfig();
        
        if( nrd == null ) {
            return DeltavCompliantUnmappedUrlImpl.getInstance();
        }
        else if( uh.isHistoryUri() ) {
            return VersionHistoryImpl.getInstance();
        }
        else if( uh.isVersionUri() ) {
            return VersionImpl.getInstance();
        }
        else if( uh.isWorkspaceUri() ) {
            return WorkspaceImpl.getInstance();
        }
        else if( uh.isWorkingresourceUri() ) {
            return WorkingImpl.getInstance();
        }
        else if( nrd.exists(DeltavConstants.PN_CHECKED_IN) ) {
            return CheckedInVersionControlledImpl.getInstance();
        }
        else if( nrd.exists(DeltavConstants.PN_CHECKED_OUT) ) {
            return CheckedOutVersionControlledImpl.getInstance();
        }
        else if( config.isPrincipal(resourcePath) ) {
            return PrincipalImpl.getInstance();
        }
        else if( nrd.propertyValueContains(WebdavConstants.PN_RESOURCETYPE, 
                WebdavConstants.E_COLLECTION) ) {
            return DeltavCompliantCollectionImpl.getInstance();
        }
        else {
            return VersionableImpl.getInstance();
        }
    }

    
    
    static public void registerComputedProperty(Kind resourceKind, 
            PropertyName name, PropertyComputer computer) {
        resourceKind.instance.addComputedProperty(name, computer);
    }
    static public void registerProtectedProperty(Kind resourceKind, 
            PropertyName name) {
        resourceKind.instance.addProtectedProperty(name);
    }
    static public void registerLiveProperty(Kind resourceKind, 
            PropertyName name) {
        resourceKind.instance.addLiveProperty(name);
    }
    static public void registerPropertyValidator(Kind resourceKind, 
            PropertyName name, PropertyValueValidator validator) {
        resourceKind.instance.addPropertyValidator(name, validator);
    }
    static public void registerDefault(Kind resourceKind, PropertyName name,
            PropertyDefaultProvider defaultProvider) {
        resourceKind.instance.addDefaultProvider(name, defaultProvider);
    }

    static public void registerReport(Kind resourceKind, String reportName) {
        resourceKind.instance.addSupportedReport(reportName);
    }
    static public void registerMethod(Kind resourceKind, String methodName) {
        resourceKind.instance.addSupportedMethod(methodName);
    }
    
    
    /**
     * Tests if the given property is protected at <em>any</em> resource kind.
     * @param name the properties name
     */
    static public boolean isProtectedProperty(PropertyName name) {
        return AbstractResourceKind.isGlobalProtectedProperty(name);
    }
    
    static void initFromDefaultConfig() {
        initFromResource("/org/apache/slide/webdav/util/resourcekind/resource_kinds.xml");
    }
    
    static void initFromResource(String resourceName) {
        initFromStream(ResourceKindManager.class.getResourceAsStream(resourceName));
    }
    
    static void initFromStream(InputStream inputStream) {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware(false);
        factory.setValidating(false);
        try {
            SAXParser parser = factory.newSAXParser();
            Populate pop = new Populate();
            Configuration configuration =
                new ConfigurationElement(pop.load(
                        new InputSource(inputStream), parser.getXMLReader()));
            
            for (Enumeration e = configuration.getConfigurations("resource-kind"); e.hasMoreElements();) {
                initResourceKind((Configuration)e.nextElement());
            }
        } catch (ParserConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SAXException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    static void initResourceKind(Configuration config) 
        throws ConfigurationException 
    {
        Kind kind = getKind(config.getAttribute("name"));
        
        if (kind == null) {
            throw new ConfigurationException("Unknown resource kind name: " + config.getAttribute("name"), config);
        }
        
        initProperties(config, kind);
        initReports(config, kind);
        initMethods(config, kind);
        
    }
    
    private static void initProperties(Configuration config, Kind kind)
    {
        Configuration subConfig = null;
        Enumeration ee = config.getConfigurations("live-properties");
        if (ee.hasMoreElements()) {
            subConfig = (Configuration)ee.nextElement();
            for(Enumeration e = subConfig.getConfigurations("live-property"); e.hasMoreElements();) {
                Configuration propConfig = (Configuration)e.nextElement();
                
                PropertyName name = PropertyName.getPropertyName(
                        propConfig.getAttribute("name"),
                        propConfig.getAttribute("namespace", "DAV:"));
                
                // check features
                boolean enabled = true;
                for(StringTokenizer t = new StringTokenizer(
                        propConfig.getAttribute("features", "")," "); t.hasMoreTokens();) {
                    if (!WebdavFeatures.isSupported(t.nextToken())) enabled = false;
                }
                if (!enabled) continue;
                
                
                StringBuffer message = new StringBuffer();
                message.append(kind + " (property: " + name);
                String p = propConfig.getAttribute("computer", null);
                if (p != null) {
                    message.append(" comupted" );
                    registerComputedProperty(kind, name, p, propConfig);
                } 
                // TODO handle properties which are not enabled but protected
                // see DAV:supported-live-property in RFC 3253
                p = propConfig.getAttribute("protected", "false");
                if ("true".equals(p)) {
                    registerProtectedProperty(kind, name);
                    message.append(" protected" );
                }
                
                p = propConfig.getAttribute("validator", null);
                if (p != null) {
                    registerPropertyValidator(kind, name, p, propConfig);
                    message.append(" validated" );
                }
                
                p = propConfig.getAttribute("default", null);
                if (p != null) {
                    message.append(" default");
                    registerDefault(kind, name, p);
                } else {
                    try {
                        Configuration defaultValue = propConfig.getConfiguration("default-value");
                        message.append(" default:" + defaultValue.getValue(""));
                        registerDefault(kind, name, new SimpleDefaultProvider(
                                name, defaultValue.getValue()));
                    } catch (ConfigurationException ex) {
                        // ignore, no default
                    }
                }
            
                // live property
                message.append(" live");
                registerLiveProperty(kind, name);
            
                message.append(")");
                log(message.toString());
            }
        }
    }

    private static void initReports(Configuration config, Kind kind) {
        Configuration subConfig = null;
        Enumeration ee = config.getConfigurations("supported-reports");
        if (ee.hasMoreElements()) {
            subConfig = (Configuration)ee.nextElement();
            for(Enumeration e = subConfig.getConfigurations("report"); e.hasMoreElements();) {
                Configuration repConfig = (Configuration)e.nextElement();
                
                // check features
                boolean enabled = true;
                for(StringTokenizer t = new StringTokenizer(
                        repConfig.getAttribute("features", "")," "); t.hasMoreTokens();) {
                    if (!WebdavFeatures.isSupported(t.nextToken())) enabled = false;
                }
                if (enabled) {
                    registerReport(kind, repConfig.getAttribute("name"));
                    log(kind + " (report: " + repConfig.getAttribute("name") + ")");
                }
            }
        }
    }
    private static void initMethods(Configuration config, Kind kind) {
        Configuration subConfig = null;
        Enumeration ee = config.getConfigurations("supported-methods");
        if (ee.hasMoreElements()) {
            subConfig = (Configuration)ee.nextElement();
            for(Enumeration e = subConfig.getConfigurations("method"); e.hasMoreElements();) {
                Configuration methodConfig = (Configuration)e.nextElement();
                
                // check features
                boolean enabled = true;
                for(StringTokenizer t = new StringTokenizer(
                        methodConfig.getAttribute("features", "")," "); t.hasMoreTokens();) {
                    if (!WebdavFeatures.isSupported(t.nextToken())) enabled = false;
                }
                if (enabled) {
                    registerMethod(kind, methodConfig.getAttribute("name"));
                    log(kind + " (method: " + methodConfig.getAttribute("name") + ")");
                }
            }
        }
    }

    private static void registerComputedProperty(Kind kind, PropertyName name, 
            String computerClass, Configuration propConfig)
    {
        try {
            Class cls = Class.forName(computerClass);
            PropertyComputer computer = (PropertyComputer)cls.newInstance();

            if (computer instanceof ConfigurablePropertyComputer) {
                ((ConfigurablePropertyComputer)computer).setPropertyName(name);
            }
            
            if (computer instanceof Configurable) {
                Configuration configuration = null;
                try {
                    configuration = propConfig.getConfiguration("computer-configuration");
                } catch (ConfigurationException e) {
                    // ignore; no configuration given, is OK too
                }
                if (configuration != null) {
                    ((Configurable)computer).configure(configuration);
                }
            }
            
            registerComputedProperty(kind, name, computer);
        } catch (ClassNotFoundException ex) {
            error("Class " + computerClass + " for property not found!");
        } catch (InstantiationException ex) {
            error("Object of class " + computerClass + 
                    " could not be instanciated!\n" + ex);
        } catch (IllegalAccessException ex) {
            error("Object of class " + computerClass + 
                    " could not be instanciated!\n" + ex);
        } catch (ClassCastException ex) {
            error("Class " + computerClass + 
                    " does not implement PropertyComputer!");
        } 
    }
    private static void registerPropertyValidator(Kind kind, 
            PropertyName name, String validatorClass, Configuration propConfig) {
        try {
            Class cls = Class.forName(validatorClass);
            PropertyValueValidator validator = (PropertyValueValidator)cls.newInstance();
            registerPropertyValidator(kind, name, validator);
            
            if (validator instanceof ConfigurablePropertyValueValidator) {
                ((ConfigurablePropertyValueValidator)validator).setPropertyName(name);
            }
            if (validator instanceof Configurable) {
                Configuration configuration = null;
                try {
                    configuration = propConfig.getConfiguration("validator-configuration");
                } catch (ConfigurationException e) {
                    // ignore; no configuration given, is OK too
                }
                if (configuration != null) {
                    ((Configurable)validator).configure(configuration);
                }
            }
        } catch (ClassNotFoundException ex) {
            error("Class " + validatorClass + " for validator not found!");
        } catch (InstantiationException ex) {
            error("Object of class " + validatorClass + 
                    " for validator could not be instanciated!\n" + ex);
        } catch (IllegalAccessException ex) {
            error("Object of class " + validatorClass + 
                    " for validator could not be instanciated!\n" + ex);
        } catch (ClassCastException ex) {
            error("Class " + validatorClass + 
                    " does not implement PropertyValueValidator!");
        }
    }
    private static void registerDefault(Kind kind, 
            PropertyName name, String defaultProviderClass) {
        try {
            Class cls = Class.forName(defaultProviderClass);
            PropertyDefaultProvider provider = (PropertyDefaultProvider)cls.newInstance();
            registerDefault(kind, name, provider);
        } catch (ClassNotFoundException ex) {
            error("Class " + defaultProviderClass + " for default not found!");
        } catch (InstantiationException ex) {
            error("Object of class " + defaultProviderClass + 
                    " for default could not be instanciated!\n" + ex);
        } catch (IllegalAccessException ex) {
            error("Object of class " + defaultProviderClass + 
                    " for default could not be instanciated!\n" + ex);
        } catch (ClassCastException ex) {
            error("Object of class " + defaultProviderClass + 
                    " does not implement PropertyDefaultProvider!");
        }
    }

    private static void log(String msg) {
        Domain.log(msg, LOG_CHANNEL, Logger.DEBUG);
    }
    private static void error(String msg) {
        Domain.log(msg, LOG_CHANNEL, Logger.ERROR);
    }
    
    public static Kind getKind(String name) {
        return (Kind)Kind.kinds.get(name);
    }
    
    public static final class Kind {
        protected static Map kinds = new HashMap();
        protected String name;
        protected AbstractResourceKind instance;
        protected Kind(String name, ResourceKind instance) {
            this.name = name;
            this.instance = (AbstractResourceKind)instance; // valid?
            kinds.put(name, this);
        }
        public String toString()
        {
            return this.name;
        }
    }


    public static void main(String[] args) {
        initFromDefaultConfig();
        
        System.out.println("------------");
        
        printConfig();
        
    }
    
    public static void printConfig() {
        for(Iterator i = Kind.kinds.values().iterator(); i.hasNext();) {
            Kind kind = (Kind)i.next();
            System.out.println(kind);
            
            System.out.println("\tlive props");
            for(Iterator j = kind.instance.getSupportedLiveProperties().iterator();j.hasNext();) {
                System.out.println("\t\t" + j.next());
            }
            System.out.println("\tprotected props");
            for(Iterator j = kind.instance.getSupportedLiveProperties(DeltavConstants.Q_PROTECTED_ONLY).iterator();j.hasNext();) {
                System.out.println("\t\t" + j.next());
            }
            System.out.println("\tcomputed props");
            for(Iterator j = kind.instance.getSupportedLiveProperties(DeltavConstants.Q_COMPUTED_ONLY).iterator();j.hasNext();) {
                System.out.println("\t\t" + j.next());
            }
        }
    }
}
