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

import org.apache.openejb.config.ServiceUtils;
import org.apache.openejb.util.Messages;

import java.util.concurrent.Semaphore;

/**
 * @version $Rev$ $Date$
 */
public class Core {

    /*
     * IMPORTANT: Do NOT refactor the anonymous threads in this class to lambda expressions.
     *
     * Reason:
     * This code runs during static class initialization. The main thread holds the
     * class-initialization lock for Core while starting worker threads and waiting
     * for them.
     *
     * If lambdas are used, the lambda body is compiled into a synthetic method
     * inside the Core class. When the spawned thread tries to execute the lambda,
     * it must access that synthetic method, which requires Core to be fully
     * initialized. However, the main thread is still holding the initialization
     * lock and waiting for the worker thread to finish.
     *
     * This results in a class-initialization deadlock:
     *   - Main thread waits for worker thread
     *   - Worker thread waits for Core initialization to complete
     *
     * Anonymous inner classes avoid this problem because they are compiled as
     * separate classes (e.g. Core$1.class) and can execute without requiring
     * Core to finish initialization.
     *
     */
    static {
        final Thread preloadMessages = new Thread() {
            @Override
            public void run() {
                new Messages("org.apache.openejb.util.resources");
                new Messages("org.apache.openejb.config");
                new Messages("org.apache.openejb.config.resources");
            }
        };
        preloadMessages.start();

        final Thread preloadServiceProviders = new Thread() {
            @Override
            public void run() {
                try {
                    ServiceUtils.getServiceProviders();
                } catch (final OpenEJBException e) {
                    // no-op
                }
            }
        };
        preloadServiceProviders.start();

        final int permits = 2 * Runtime.getRuntime().availableProcessors() + 1;
        final Semaphore semaphore = new Semaphore(0);
        final ClassLoader loader = OpenEjbContainer.class.getClassLoader();

        try {
            Class.forName("org.apache.tomee.catalina.Warmup", true, loader);
            // ok preloading already done
        } catch (final Throwable e) {
            try { // logging classes should be loaded before any other classes so do it here synchronously
                Class.forName("org.apache.openejb.util.Logger", true, loader);
            } catch (final Throwable ignored) {
                // no-op
            }

            final String[] classes = {
                    "org.slf4j.LoggerFactory",
                    "org.slf4j.impl.StaticLoggerBinder",

                    "org.apache.openejb.config.sys.JaxbJavaee",
                    "org.apache.bval.jsr.ApacheValidationProvider",
                    "org.apache.bval.jsr.ApacheValidatorFactory",
                    "org.apache.bval.jsr.ConstraintAnnotationAttributes",
                    "org.apache.bval.jsr.ConstraintDefaults",
                    "org.apache.bval.jsr.groups.GroupsComputer",
                    "org.apache.bval.jsr.xml.ValidationMappingParser",
                    "org.apache.bval.util.PrivilegedActions",
                    "org.apache.geronimo.transaction.manager.TransactionManagerImpl",
                    "org.apache.openejb.InterfaceType",
                    "org.apache.openejb.assembler.classic.Assembler",
                    "org.apache.openejb.assembler.classic.AssemblerTool",
                    "org.apache.openejb.cdi.CdiBuilder",
                    "org.apache.openejb.cdi.ThreadSingletonServiceImpl",
                    "org.apache.openejb.config.AppValidator",
                    "org.apache.openejb.config.AnnotationDeployer",
                    "org.apache.openejb.config.AutoConfig",
                    "org.apache.openejb.config.ConfigurationFactory",
                    "org.apache.openejb.config.MBeanDeployer",
                    "org.apache.openejb.config.PersistenceContextAnnFactory",
                    "org.apache.openejb.core.ServerFederation",
                    "org.apache.openejb.core.ivm.EjbHomeProxyHandler$1",
                    "org.apache.openejb.core.ivm.EjbHomeProxyHandler$MethodType",
                    "org.apache.openejb.core.managed.ManagedContainer$MethodType",
                    "org.apache.openejb.loader.FileUtils",
                    "org.apache.openejb.loader.IO",
                    "org.apache.openejb.loader.SystemInstance",
                    "org.apache.openejb.monitoring.StatsInterceptor",
                    "org.apache.openejb.persistence.JtaEntityManagerRegistry",
                    "org.apache.openejb.util.Join",
                    "org.apache.openejb.util.JuliLogStreamFactory",
                    "org.apache.openejb.util.LogCategory",
                    "org.apache.openejb.util.Messages",
                    "org.apache.openejb.util.SafeToolkit",
                    "org.apache.openejb.util.StringTemplate",
                    "org.apache.openejb.util.proxy.ProxyManager",
                    "org.apache.openjpa.enhance.PCRegistry",
                    "org.apache.openjpa.lib.util.Localizer",
                    "org.apache.webbeans.logger.WebBeansLoggerFacade",
                    "org.apache.xbean.naming.reference.SimpleReference",
                    "org.apache.xbean.propertyeditor.PropertyEditors",
                    "org.apache.xbean.propertyeditor.ReferenceIdentityMap",
                    "org.apache.xbean.recipe.ReflectionUtil"
            };

            final int part = Math.max(1, (int) Math.round(classes.length * 1. / permits));
            for (int i = 0; i < permits; i++) {
                final int current = i;
                final int offset = i * part;
                final Thread thread = new Thread() {
                    {
                        setName("core-" + current);
                    }

                    @Override
                    public void run() {
                        int max = offset + part;
                        if (current == permits - 1) { // last one
                            max = classes.length;
                        }

                        for (int c = offset; c < max; c++) {
                            try {
                                Class.forName(classes[c], true, loader);
                            } catch (final Throwable e) {
                                // no-op
                            }
                        }
                        semaphore.release();
                    }
                };
                thread.setDaemon(true);
                thread.start();
            }
            try {
                semaphore.acquire(permits);
            } catch (final InterruptedException ie) {
                Thread.interrupted();
            }
        }

        try {
            preloadServiceProviders.join();
            preloadMessages.join();
        } catch (final InterruptedException e) {
            Thread.interrupted();
        }
    }

    public static void warmup() {
    }
}
