/*
 * 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 javax.validation;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import javax.validation.bootstrap.GenericBootstrap;
import javax.validation.bootstrap.ProviderSpecificBootstrap;
import javax.validation.spi.BootstrapState;
import javax.validation.spi.ValidationProvider;

import org.apache.geronimo.osgi.locator.ProviderLocator;

/**
 * Note: From Section 4.4.5 Validation of the 1.0 PFD Spec - Validation
 * implementations may only provide the following three public static methods:
 * buildDefaultValidatorFactory(), byDefaultProvider(), byProvider()
 *
 * @version $Rev$ $Date$
 */
public class Validation {

    public static ValidatorFactory buildDefaultValidatorFactory() {
        return byDefaultProvider().configure().buildValidatorFactory();
    }

    public static GenericBootstrap byDefaultProvider() {
        return new GenericBootstrapImpl();
    }

    public static <T extends Configuration<T>, U extends ValidationProvider<T>>
            ProviderSpecificBootstrap<T> byProvider(Class<U> providerType) {
        return new ProviderSpecificBootstrapImpl<T, U>(providerType);
    }

    /*
     * (non-Javadoc) See Section 4.4.5 Validation - Must be private
     *
     * Geronimo implementation specific code.
     */
	private static class ProviderSpecificBootstrapImpl<T extends Configuration<T>, U extends ValidationProvider<T>>
        implements ProviderSpecificBootstrap<T> {

		private final Class<U> providerClass;
		private ValidationProviderResolver vpResolver;

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.bootstrap.ProviderSpecificBootstrap#ProviderSpecificBootstrap(Class<T>)
         */

		public ProviderSpecificBootstrapImpl(Class<U> validationProviderClass) {
			providerClass = validationProviderClass;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.bootstrap.ProviderSpecificBootstrap#providerResolver(javax.validation.ValidationProviderResolver)
         */
        public ProviderSpecificBootstrap<T> providerResolver(ValidationProviderResolver resolver) {
            vpResolver = resolver;
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.bootstrap.ProviderSpecificBootstrap#configure()
         */
        public T configure() {
            if (providerClass == null)
                throw new ValidationException("No resolver provided");

            // create a default resolver if not supplied by providerResolver()
            GenericBootstrapImpl impl = new GenericBootstrapImpl();
            if ( vpResolver == null )
                vpResolver = impl.getDefaultValidationProviderResolver();
            else
                impl.providerResolver(vpResolver);

            // check each provider discovered by the resolver
            for (ValidationProvider<?> vProvider : vpResolver.getValidationProviders()) {
                if (providerClass.isAssignableFrom(vProvider.getClass())) {
                    // Create a ValidationProvider<T> from the above bootstrap impl
                    // and configurationType
                    return providerClass.cast(vProvider).createSpecializedConfiguration(impl);
                }
            }

            // throw a Spec required exception
            throw new ValidationException("No provider found for " + providerClass);
        }
    }

    /*
     * (non-Javadoc) See Section 4.4.5 Validation - Must be private
     *
     * Geronimo implementation specific code.
     */
    private static class GenericBootstrapImpl implements GenericBootstrap, BootstrapState {

        private ValidationProviderResolver vpDefaultResolver;
        private ValidationProviderResolver vpResolver;

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.bootstrap.GenericBootstrap#providerResolver(javax.validation.ValidationProviderResolver)
         */
        public GenericBootstrap providerResolver(ValidationProviderResolver resolver) {
            vpResolver = resolver;
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.spi.BootstrapState#getValidationProviderResolver()
         */
        public ValidationProviderResolver getValidationProviderResolver() {
            return vpResolver;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.spi.BootstrapState#getDefaultValidationProviderResolver()
         */
        public ValidationProviderResolver getDefaultValidationProviderResolver() {
            if (vpDefaultResolver == null)
                vpDefaultResolver = new DefaultValidationProviderResolver();
            return vpDefaultResolver;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.bootstrap.GenericBootstrap#configure()
         */
        public Configuration<?> configure() {
            ValidationProviderResolver resolv = vpResolver;
            try {
                if (resolv == null)
                    resolv = getDefaultValidationProviderResolver();
                return resolv.getValidationProviders().get(0).createGenericConfiguration(this);
            } catch (Exception e) {
                throw new ValidationException("Could not create Configuration.", e);
            }
        }
    }

    /*
     * (non-Javadoc) See Section 4.4.5 Validation - Must be private
     *
     * Geronimo implementation specific code.
     */
    private static class DefaultValidationProviderResolver implements ValidationProviderResolver {
        // cache of providers per class loader
        private volatile WeakHashMap<ClassLoader, List<ValidationProvider<?>>> providerCache =
            new WeakHashMap<ClassLoader, List<ValidationProvider<?>>>();

        /*
         * (non-Javadoc)
         *
         * @see javax.validation.ValidationProviderResolver#getValidationProviders()
         */
        public List<ValidationProvider<?>> getValidationProviders() {
            List<ValidationProvider<?>> providers;

            // get our class loader
            ClassLoader cl = PrivClassLoader.get(null);
            if (cl == null)
                cl = PrivClassLoader.get(DefaultValidationProviderResolver.class);

            // use any previously cached providers
            providers = providerCache.get(cl);
            if (providers == null) {
                // need to discover and load them for this class loader
                providers = new ArrayList<ValidationProvider<?>>();
                try {
                    List<Object> serviceProviders = ProviderLocator.getServices(ValidationProvider.class.getName(), this.getClass(), cl);
                    for (Object provider : serviceProviders) {
                        // create an instance to return
                        providers.add((ValidationProvider<?>) provider);
                    }
                } catch (ClassNotFoundException e) {
                    throw new ValidationException("Failed to load provider", e);
                } catch (InstantiationException e) {
                    throw new ValidationException("Failed to instantiate provider", e);
                } catch (IllegalAccessException e) {
                    throw new ValidationException("Failed to access provider", e);
                } catch (ClassCastException e) {
                    throw new ValidationException("Invalid provider definition", e);
                } catch (Exception e) {
                    throw new ValidationException("Failed to instantiate provider", e);
                }

                // cache the discovered providers
                providerCache.put(cl, providers);
            }

            // caller must handle the case of no providers found
            return providers;
        }

        private static class PrivClassLoader implements PrivilegedAction<ClassLoader> {
            private final Class<?> c;

            public static ClassLoader get(Class<?> c) {
                final PrivClassLoader action = new PrivClassLoader(c);
                if (System.getSecurityManager() != null)
                    return AccessController.doPrivileged(action);
                else
                    return action.run();
            }

            private PrivClassLoader(Class<?> c) {
                this.c = c;
            }

            public ClassLoader run() {
                if (c != null)
                    return c.getClassLoader();
                else
                    return Thread.currentThread().getContextClassLoader();
            }
        }
    }
}

