/*
 * Copyright 2010 JBoss Inc
 *
 * 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.drools.jsr94.rules.repository;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Properties;

/**
 * Load the <code>RuleExecutionSetRepository</code> using the following algorithm.
 * 
 * 1. If a resource with the name of META-INF/services/org.drools.jsr94.rules.repository.RuleExecutionSetRepository exists,
 * then its first line, if present, is used as the UTF-8 encoded name of the implementation class.
 * 
 * 2. If the drools.properties file exists in the classpath and it is readable by the 
 * java.util.Properties.load(InputStream) method and it contains an entry whose key is 
 * org.drools.jsr94.rules.repository.RuleExecutionSetRepository, then the value of that
 * entry is used as the name of the implementation class.
 * 
 * 3. If a system property with the name org.drools.jsr94.rules.repository.RuleExecutionSetRepository
 * is defined, then its value is used as the name of the implementation class.
 * 
 * 4. Finally, a default implementation class name, if provided, is used.
 * 
 * @version $Revision$ $Date$
 */
public abstract class RuleExecutionSetRepositoryLoader
{
    /**
     * Loads the <code>RuleExecutionSetRepository</code> using the
     * algorithm described above.
     *
     * @param defaultFactoryName the className of the default
     *  <code>RuleExecutionSetRepository</code> implementation
     * @return
     */
    public static RuleExecutionSetRepository loadRuleExecutionSetRepository(
            String defaultFactoryName) {

        Object factory = null;
        String factoryName = null;
        ClassLoader cL = Thread.currentThread().getContextClassLoader();

        // Use the Services API (as detailed in the JAR specification), if available, to determine the classname.
        String propertyName = "org.drools.jsr94.rules.repository.RuleExecutionSetRepository";
        String fileName = "META-INF/services/" + propertyName;
        InputStream in = cL.getResourceAsStream(fileName);

        if (in != null) {
            BufferedReader reader = null;

            try {
                reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
                factoryName = reader.readLine();

                if (factoryName != null) {
                    factory = createFactory(cL, factoryName);
                }

            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException("Failed to load " + propertyName + ": " + factoryName, e);
            } catch (IOException e) {
                throw new IllegalStateException("Failed to load " + propertyName + ": " + factoryName, e);
            } finally {
                close(reader);
            }
        }

        // Use the properties file "drools.properties"
        if (factory == null) {
            // TODO
        }

        // Use system property
        if (factory == null) {
            PrivilegedAction action = new PropertyAccessAction(propertyName);
            factoryName = (String)AccessController.doPrivileged(action);

            if (factoryName != null) {
                factory = createFactory(cL, factoryName);
            }
        }

        // Use the default factory implementation class.
        if (factory == null && defaultFactoryName != null) {
            factory = createFactory(cL, defaultFactoryName);
        }

        return (RuleExecutionSetRepository)factory;
    }

    /**
     * TODO
     *
     * @param closeable
     */
    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                // ignored
            }
        }
    }

    /**
     * TODO
     *
     * @param cL
     * @param factoryName
     * @return
     */
    private static Object createFactory(ClassLoader cL, String factoryName) {
        try {
            Class factoryClass = cL.loadClass(factoryName);
            return factoryClass.newInstance();
        } catch (Throwable t) {
            throw new IllegalStateException("Failed to load: " + factoryName, t);
        }
    }

    private static class PropertyAccessAction implements PrivilegedAction
    {
        private String name;

        PropertyAccessAction(String name) {
            this.name = name;
        }

        public Object run() {
            return System.getProperty(name);
        }
    }

    private static class PropertyFileAccessAction implements PrivilegedAction
    {
        private String fileName;

        PropertyFileAccessAction(String fileName) {
            this.fileName = fileName;
        }

        public Object run() {
            InputStream in = null;

            try {
                in = new FileInputStream(fileName);
                Properties props = new Properties();
                props.load(in);
                return props;
            } catch (IOException e) {
                throw new SecurityException("Cannot load properties: " + fileName, e);
            } finally {
                close(in);
            }
        }
    }
}
