/**
 * OW2 Specifications
 * Copyright (C) 2010 Bull S.A.S.
 * Contact: easybeans@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: Validation.java 5871 2011-05-05 12:11:35Z benoitf $
 * --------------------------------------------------------------------------
 */
/*
 * 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.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
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.ow2.spec.ee.validation.ValidationProviderResolverHolder;


/**
 * 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();
                }
                List<ValidationProvider<?>> providers = resolv.getValidationProviders();
                if (providers == null || providers.size() == 0) {
                    throw new ValidationException("No Validation Provider Found. Check that a Validation Provider implementation is provided");
                }
                
                return providers.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 = ValidationProviderResolverHolder.getValidationProviderResolver().getValidationProviders();

                // 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();
            }
        }
    }
}

