/*
 * 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.catalina.storeconfig;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.digester.Digester;
import org.xml.sax.SAXException;

/**
 * <b>XML Format </b>
 *
 * <pre>
 * {@code
 *       <Registry name="" encoding="UTF8" >
 *         <Description
 *             tag="Server"
 *             standard="true"
 *             default="true"
 *             tagClass="org.apache.catalina.core.StandardServer"
 *             storeFactoryClass="org.apache.catalina.storeconfig.StandardServerSF">
 *           <TransientAttributes>
 *             <Attribute></Attribute>
 *           </TransientAttributes>
 *           <TransientChildren>
 *             <Child></Child>
 *           </TransientChildren>
 *         </Description>
 *   ...
 *       </Registry>
 * }
 * </pre>
 *
 *
 * Convention:
 * <ul>
 * <li>Factories at subpackage <i>org.apache.catalina.core.storeconfig.xxxSF
 * </i>.</li>
 * <li>Element name are the unique Class name</li>
 * <li>SF for StoreFactory</li>
 * <li>standard implementation is false</li>
 * </ul>
 * other things:
 * <ul>
 * <li>Registry XML format is a very good option</li>
 * <li>Store format is not fix</li>
 * <li>We hope with the parent declaration we can build recursive child store
 * operation //dream</li>
 * <li>Problem is to access child data from array,collections or normal detail
 * object</li>
 * <li>Default definitions for Listener, Valve Resource? - Based on interface
 * type!</li>
 * </ul>
 */
public class StoreLoader {
    private static Log log = LogFactory.getLog(StoreLoader.class);

    /**
     * The <code>Digester</code> instance used to parse registry descriptors.
     */
    protected static final Digester digester = createDigester();

    private StoreRegistry registry;

    private URL registryResource ;

    /**
     * @return Returns the registry.
     */
    public StoreRegistry getRegistry() {
        return registry;
    }

    /**
     * @param registry
     *            The registry to set.
     */
    public void setRegistry(StoreRegistry registry) {
        this.registry = registry;
    }

    /**
     * Create and configure the Digester we will be using for setup store
     * registry.
     * @return the XML digester that will be used to parse the configuration
     */
    protected static Digester createDigester() {
        long t1 = System.currentTimeMillis();
        // Initialize the digester
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.setClassLoader(StoreRegistry.class.getClassLoader());

        // Configure the actions we will be using
        digester.addObjectCreate("Registry",
                "org.apache.catalina.storeconfig.StoreRegistry", "className");
        digester.addSetProperties("Registry");
        digester
                .addObjectCreate("Registry/Description",
                        "org.apache.catalina.storeconfig.StoreDescription",
                        "className");
        digester.addSetProperties("Registry/Description");
        digester.addRule("Registry/Description", new StoreFactoryRule(
                "org.apache.catalina.storeconfig.StoreFactoryBase",
                "storeFactoryClass",
                "org.apache.catalina.storeconfig.StoreAppender",
                "storeAppenderClass"));
        digester.addSetNext("Registry/Description", "registerDescription",
                "org.apache.catalina.storeconfig.StoreDescription");
        digester.addCallMethod("Registry/Description/TransientAttribute",
                "addTransientAttribute", 0);
        digester.addCallMethod("Registry/Description/TransientChild",
                "addTransientChild", 0);

        long t2 = System.currentTimeMillis();
        if (log.isDebugEnabled())
            log.debug("Digester for server-registry.xml created " + (t2 - t1));
        return (digester);

    }

    /**
     * Find main configuration file.
     * @param aFile File name, absolute or relative
     *  to <code>${catalina.base}/conf</code>, if not specified
     *  <code>server-registry.xml</code> is used
     * @return The file
     */
    protected File serverFile(String aFile) {

        if (aFile == null || aFile.length() < 1)
            aFile = "server-registry.xml";
        File file = new File(aFile);
        if (!file.isAbsolute())
            file = new File(System.getProperty("catalina.base") + "/conf",
                    aFile);
        try {
            file = file.getCanonicalFile();
        } catch (IOException e) {
            log.error(e);
        }
        return (file);
    }

    /**
     * Load main configuration file from external source.
     *
     * @param aURL URL to the configuration file
     */
    public void load(String aURL) {
        synchronized (digester) {
            File aRegistryFile = serverFile(aURL);
            try {
                registry = (StoreRegistry) digester.parse(aRegistryFile);
                registryResource = aRegistryFile.toURI().toURL();
            } catch (IOException e) {
                log.error(e);
            } catch (SAXException e) {
                log.error(e);
            }
        }

    }

    /**
     * Load from defaults
     * <ul>
     * <li>System Property URL catalina.storeregistry</li>
     * <li>File ${catalina.base}/conf/server-registry.xml</li>
     * <li>class resource org/apache/catalina/storeconfig/server-registry.xml
     * </li>
     * </ul>
     */
    public void load() {

        InputStream is = null;
        registryResource = null ;
        try {
            String configUrl = getConfigUrl();
            if (configUrl != null) {
                is = (new URL(configUrl)).openStream();
                if (log.isInfoEnabled())
                    log.info("Find registry server-registry.xml from system property at url "
                            + configUrl);
                registryResource = new URL(configUrl);
            }
        } catch (Throwable t) {
            // Ignore
        }
        if (is == null) {
            try {
                File home = new File(getCatalinaBase());
                File conf = new File(home, "conf");
                File reg = new File(conf, "server-registry.xml");
                is = new FileInputStream(reg);
                if (log.isInfoEnabled())
                    log.info("Find registry server-registry.xml at file "
                            + reg.getCanonicalPath());
                registryResource = reg.toURI().toURL();
            } catch (Throwable t) {
                // Ignore
            }
        }
        if (is == null) {
            try {
                is = StoreLoader.class
                        .getResourceAsStream("/org/apache/catalina/storeconfig/server-registry.xml");
                if (log.isDebugEnabled())
                    log.debug("Find registry server-registry.xml at classpath resource");
                registryResource = StoreLoader.class
                    .getResource("/org/apache/catalina/storeconfig/server-registry.xml");

            } catch (Throwable t) {
                // Ignore
            }
        }
        if (is != null) {
            try {
                synchronized (digester) {
                    registry = (StoreRegistry) digester.parse(is);
                }
            } catch (Throwable t) {
                log.error(t);
            } finally {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
        }
        if (is == null) {
            log.error("Failed to load server-registry.xml");
        }
    }

    /**
     * @return the catalina.home environment variable.
     */
    private static String getCatalinaHome() {
        return System.getProperty("catalina.home", System
                .getProperty("user.dir"));
    }

    /**
     * @return the catalina.base environment variable.
     */
    private static String getCatalinaBase() {
        return System.getProperty("catalina.base", getCatalinaHome());
    }

    /**
     * @return the configuration URL.
     */
    private static String getConfigUrl() {
        return System.getProperty("catalina.storeconfig");
    }

    /**
     * @return the registryResource.
     */
    public URL getRegistryResource() {
        return registryResource;
    }
}
