// 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
// 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.cloudstack.test.utils;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ComponentInstantiationPostProcessor;
import com.cloud.utils.component.ComponentMethodInterceptor;
import com.cloud.utils.db.TransactionContextBuilder;
import com.cloud.utils.exception.CloudRuntimeException;

public class SpringUtils {

    /**
     * This method allows you to use @ComponentScan for your unit testing but
     * it limits the scope of the classes found to the class specified in
     * the @ComponentScan annotation.
     *
     * Without using this method, the default behavior of @ComponentScan is
     * to actually scan in the package of the class specified rather than
     * only the class. This can cause extra classes to be loaded which causes
     * the classes these extra classes depend on to be loaded. The end effect
     * is often most of the project gets loaded.
     *
     * In order to use this method properly, you must do the following: <li>
     *   - Specify @ComponentScan with basePackageClasses, includeFilters, and
     *     useDefaultFilters=true.  See the following example.
     *
     * <pre>
     *     @ComponentScan(basePackageClasses={AffinityGroupServiceImpl.class, ActionEventUtils.class},
     *     includeFilters={@Filter(value=TestConfiguration.Library.class, type=FilterType.CUSTOM)},
     *     useDefaultFilters=false)
     * </pre>
     *
     *   - Create a Library class and use that to call this method.  See the
     *     following example.  The Library class you define here is the Library
     *     class being added in the filter above.
     *
     * <pre>
     * public static class Library implements TypeFilter {
     *      @Override
     *      public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException {
     *          ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class);
     *          return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs);
     *      }
     * }
     * </pre>
     *
     * @param clazzName name of the class that should be included in the Spring components
     * @param cs ComponentScan annotation that was declared on the configuration
     *
     * @return
     */
    public static boolean includedInBasePackageClasses(String clazzName, ComponentScan cs) {
        Class<?> clazzToCheck;
        try {
            clazzToCheck = Class.forName(clazzName);
        } catch (ClassNotFoundException e) {
            throw new CloudRuntimeException("Unable to find " + clazzName);
        }
        Class<?>[] clazzes = cs.basePackageClasses();
        for (Class<?> clazz : clazzes) {
            if (clazzToCheck.isAssignableFrom(clazz)) {
                return true;
            }
        }
        return false;
    }

    public static class CloudStackTestConfiguration {

        @Bean
        public ComponentContext componentContext() {
            return new ComponentContext();
        }

        @Bean
        public TransactionContextBuilder transactionContextBuilder() {
            return new TransactionContextBuilder();
        }

        @Bean
        public ComponentInstantiationPostProcessor instantiatePostProcessor() {
            ComponentInstantiationPostProcessor processor = new ComponentInstantiationPostProcessor();

            List<ComponentMethodInterceptor> interceptors = new ArrayList<ComponentMethodInterceptor>();
            interceptors.add(new TransactionContextBuilder());
            processor.setInterceptors(interceptors);

            return processor;
        }
    }
}
