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

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.openejb.ClassLoaderUtil;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.api.LocalClient;
import org.apache.openejb.api.RemoteClient;
import org.apache.openejb.cdi.CompositeBeans;
import org.apache.openejb.classloader.ClassLoaderConfigurer;
import org.apache.openejb.classloader.WebAppEnricher;
import org.apache.openejb.config.event.BeforeDeploymentEvent;
import org.apache.openejb.config.event.EnhanceScannableUrlsEvent;
import org.apache.openejb.config.sys.Resources;
import org.apache.openejb.core.EmptyResourcesClassLoader;
import org.apache.openejb.core.ParentClassLoaderFinder;
import org.apache.openejb.jee.Application;
import org.apache.openejb.jee.ApplicationClient;
import org.apache.openejb.jee.Beans;
import org.apache.openejb.jee.Connector;
import org.apache.openejb.jee.EjbJar;
import org.apache.openejb.jee.FacesConfig;
import org.apache.openejb.jee.JavaWsdlMapping;
import org.apache.openejb.jee.JspConfig;
import org.apache.openejb.jee.Module;
import org.apache.openejb.jee.Taglib;
import org.apache.openejb.jee.TldTaglib;
import org.apache.openejb.jee.WebApp;
import org.apache.openejb.jee.WebserviceDescription;
import org.apache.openejb.jee.Webservices;
import org.apache.openejb.jee.oejb3.OpenejbJar;
import org.apache.openejb.loader.FileUtils;
import org.apache.openejb.loader.IO;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.sxc.ApplicationXml;
import org.apache.openejb.util.AnnotationFinder;
import org.apache.openejb.util.JarExtractor;
import org.apache.openejb.util.JavaSecurityManagers;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.URLs;
import org.apache.xbean.finder.IAnnotationFinder;
import org.apache.xbean.finder.ResourceFinder;
import org.apache.xbean.finder.UrlSet;
import org.apache.xbean.finder.archive.ClassesArchive;
import org.apache.xbean.finder.filter.Filter;
import org.apache.xbean.finder.filter.Filters;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

/**
 * @version $Revision$ $Date$
 */
public class DeploymentLoader implements DeploymentFilterable {
    public static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_STARTUP_CONFIG, "org.apache.openejb.util.resources");
    public static final String OPENEJB_ALTDD_PREFIX = "openejb.altdd.prefix";

    static final String META_INF = "META-INF/";

    public static final String EAR_WEBAPP_PERSISTENCE_XML_JARS = "ear-webapp-persistence-xml-jars";
    public static final String EAR_SCOPED_CDI_BEANS = "ear-scoped-cdi-beans_";
    public static final String RAR_URLS_KEY = "rar-urls";
    public static final String URLS_KEY = "urls";

    private static final String RESOURCES_XML = "resources.xml";
    private static final String WEB_FRAGMENT_XML = "web-fragment.xml";

    private final boolean scanManagedBeans = true;
    private static final Collection<String> KNOWN_DESCRIPTORS = Arrays.asList("app-ctx.xml", "module.properties", "application.properties", "web.xml", "ejb-jar.xml", "openejb-jar.xml", "env-entries.properties", "beans.xml", "ra.xml", "application.xml", "application-client.xml", "persistence-fragment.xml", "persistence.xml", "validation.xml", NewLoaderLogic.EXCLUSION_FILE);
    private static String ALTDD = SystemInstance.get().getOptions().get(OPENEJB_ALTDD_PREFIX, (String) null);
    private volatile List<URL> containerUrls = null;

    @Deprecated // use load(File, ExternalConfiguration)
    public AppModule load(final File jarFile) throws OpenEJBException {
        return load(jarFile, null);
    }

    /**
     * @param jarFile the app file (war, jar, ear)
     * @param config potentially some more config, mainly used when linking to another system like tomcat to enrich the conf we can guess
     * @return the loaded module
     */
    public AppModule load(final File jarFile, final ExternalConfiguration config) throws OpenEJBException {
        // verify we have a valid file
        final String jarPath;
        try {
            jarPath = jarFile.getCanonicalPath();
        } catch (final IOException e) {
            throw new OpenEJBException("Invalid application file path " + jarFile, e);
        }

        final URL baseUrl = getFileUrl(jarFile);

        // create a class loader to use for detection of module type
        // do not use this class loader for any other purposes... it is
        // non-temp class loader and usage will mess up JPA
        ClassLoader doNotUseClassLoader = null;// = ClassLoaderUtil.createClassLoader(jarPath, new URL[]{baseUrl}, OpenEJB.class.getClassLoader());

        try {
            // determine the module type
            final Class<? extends DeploymentModule> moduleClass;

            try {
                doNotUseClassLoader = ClassLoaderUtil.createClassLoader(jarPath, new URL[]{baseUrl}, getOpenEJBClassLoader());
                moduleClass = discoverModuleType(baseUrl, ClassLoaderUtil.createTempClassLoader(doNotUseClassLoader), true);
            } catch (final Exception e) {
                throw new UnknownModuleTypeException("Unable to determine module type for jar: " + baseUrl.toExternalForm(), e);
            }

            if (ResourcesModule.class.equals(moduleClass)) {
                final AppModule appModule = new AppModule(null, jarPath);
                final ResourcesModule module = new ResourcesModule();
                module.getAltDDs().put(RESOURCES_XML, baseUrl);
                ReadDescriptors.readResourcesXml(module);
                module.initAppModule(appModule);
                // here module is no more useful since everything is in the appmodule
                return appModule;
            }

            //We always load AppModule, as it somewhat likes a wrapper module
            if (AppModule.class.equals(moduleClass)) {
                return createAppModule(jarFile, jarPath);
            }

            if (EjbModule.class.equals(moduleClass)) {
                final URL[] urls = new URL[]{baseUrl};

                SystemInstance.get().fireEvent(new BeforeDeploymentEvent(urls));

                final ClassLoader classLoader = ClassLoaderUtil.createTempClassLoader(jarPath, urls, getOpenEJBClassLoader());

                final AppModule appModule;
                //final Class<? extends DeploymentModule> o = EjbModule.class;
                final EjbModule ejbModule = createEjbModule(baseUrl, jarPath, classLoader);

                // wrap the EJB Module with an Application Module
                appModule = new AppModule(ejbModule);

                addPersistenceUnits(appModule, baseUrl);

                return appModule;
            }

            if (ClientModule.class.equals(moduleClass)) {
                final String jarLocation = URLs.toFilePath(baseUrl);
                final ClientModule clientModule = createClientModule(baseUrl, jarLocation, getOpenEJBClassLoader(), null);

                // Wrap the resource module with an Application Module
                return new AppModule(clientModule);
            }

            if (ConnectorModule.class.equals(moduleClass)) {
                final String jarLocation = URLs.toFilePath(baseUrl);
                final ConnectorModule connectorModule = createConnectorModule(jarLocation, jarLocation, getOpenEJBClassLoader(), null);
                if (connectorModule != null) {
                    final List<ConnectorModule> connectorModules = new ArrayList<>();

                    // let it be able to deploy the same connector several times
                    final String id = connectorModule.getModuleId();
                    if (!"true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.connector." + id + ".skip-default", "false"))) {
                        connectorModules.add(connectorModule);
                    }

                    final String aliases = SystemInstance.get().getProperty("openejb.connector." + id + ".aliases");
                    if (aliases != null) {
                        for (final String alias : aliases.split(",")) {
                            final ConnectorModule aliasModule = createConnectorModule(jarLocation, jarLocation, getOpenEJBClassLoader(), alias);
                            connectorModules.add(aliasModule);
                        }
                    }


                    // Wrap the resource module with an Application Module
                    final AppModule appModule = new AppModule(connectorModules.toArray(new ConnectorModule[connectorModules.size()]));

                    return appModule;
                }
            }

            if (WebModule.class.equals(moduleClass)) {
                final File file = URLs.toFile(baseUrl);

                // Standalone Web Module
                final WebModule webModule = createWebModule(file.getAbsolutePath(), baseUrl, getOpenEJBClassLoader(), getContextRoot(), getModuleName(), config);
                // important to use the webapp classloader here otherwise each time we'll check something using loadclass it will fail (=== empty classloader)
                final AppModule appModule = new AppModule(webModule.getClassLoader(), file.getAbsolutePath(), new Application(), true);
                addWebModule(webModule, appModule);

                addWebModuleDescriptors(baseUrl, webModule, appModule);

                appModule.setStandloneWebModule();
                appModule.setDelegateFirst(true); // force it for webapps
                return appModule;
            }

            if (PersistenceModule.class.equals(moduleClass)) {
                final String jarLocation = URLs.toFilePath(baseUrl);
                final ClassLoader classLoader = ClassLoaderUtil.createTempClassLoader(jarPath, new URL[]{baseUrl}, getOpenEJBClassLoader());

                // wrap the EJB Module with an Application Module
                final AppModule appModule = new AppModule(classLoader, jarLocation);

                // Persistence Units
                addPersistenceUnits(appModule, baseUrl);

                return appModule;
            }

            throw new UnsupportedModuleTypeException("Unsupported module type: " + moduleClass.getSimpleName());

        } finally {
            // if the application was unpacked appId used to create this class loader will be wrong
            // We can safely destroy this class loader in either case, as it was not use by any modules
            if (null != doNotUseClassLoader) {
                ClassLoaderUtil.destroyClassLoader(doNotUseClassLoader);
            }
        }
    }

    public static void addWebModuleDescriptors(final URL baseUrl, final WebModule webModule, final AppModule appModule) throws OpenEJBException {
        final List<URL> urls = webModule.getScannableUrls();
        final ResourceFinder finder = new ResourceFinder("", urls.toArray(new URL[urls.size()]));
        final Map<String, Object> otherDD = new HashMap<>(getDescriptors(finder, false));

        // "persistence.xml" is done separately since we manage a list of url and not s single url
        try {
            final List<URL> persistenceXmls = finder.findAll(META_INF + "persistence.xml");
            if (persistenceXmls.size() >= 1) {
                final URL old = (URL) otherDD.get("persistence.xml");
                if (old != null && !persistenceXmls.contains(old)) {
                    persistenceXmls.add(old);
                }
                otherDD.put("persistence.xml", persistenceXmls);
            }
        } catch (final IOException e) {
            // ignored
        }

        addConnectorModules(appModule, webModule);

        addWebPersistenceDD("persistence.xml", otherDD, appModule);
        addWebPersistenceDD("persistence-fragment.xml", otherDD, appModule);
        addPersistenceUnits(appModule, baseUrl);
        addWebFragments(webModule, urls);
    }

    private static void addConnectorModules(final AppModule appModule, final WebModule webModule) throws OpenEJBException {
        // WEB-INF
        if (webModule.getAltDDs().containsKey("ra.xml")) {
            final String jarLocation = new File(webModule.getJarLocation(), "/WEB-INF/classes").getAbsolutePath();
            final ConnectorModule connectorModule = createConnectorModule(jarLocation, jarLocation, webModule.getClassLoader(), webModule.getModuleId() + "RA", (URL) webModule.getAltDDs().get("ra.xml"));
            if (connectorModule != null) {
                appModule.getConnectorModules().add(connectorModule);
            }
        }

        // .rar
        for (final URL url : webModule.getRarUrls()) {
            try {
                final File file = URLs.toFile(url);
                if (file.getName().endsWith(".rar")) {
                    final String jarLocation = file.getAbsolutePath();
                    final ConnectorModule connectorModule = createConnectorModule(jarLocation, jarLocation, webModule.getClassLoader(), null);
                    if (connectorModule != null) {
                        appModule.getConnectorModules().add(connectorModule);
                    }
                }
            } catch (final Exception e) {
                LOGGER.error("error processing url " + url.toExternalForm(), e);
            }
        }

        for (final URL url : webModule.getScannableUrls()) {
            try {
                final File file = URLs.toFile(url);
                if (file.getName().endsWith(".jar")) {
                    try (JarFile jarFile = new JarFile(file)) {

                        // TODO: better management of altdd
                        String name = (ALTDD != null ? ALTDD + "." : "") + "ra.xml";

                        JarEntry entry = jarFile.getJarEntry(name);
                        if (entry == null) {
                            name = "META-INF/" + name;
                            entry = jarFile.getJarEntry(name);
                        }
                        if (entry == null) {
                            continue;
                        }

                        final String jarLocation = file.getAbsolutePath();
                        final ConnectorModule connectorModule = createConnectorModule(jarLocation, jarLocation, webModule.getClassLoader(), null);
                        if (connectorModule != null) {
                            appModule.getConnectorModules().add(connectorModule);
                        }
                    }
                }
            } catch (final Exception e) {
                LOGGER.error("error processing url " + url.toExternalForm(), e);
            }
        }
    }

    protected ClassLoader getOpenEJBClassLoader() {
        return ParentClassLoaderFinder.Helper.get();
    }

    @SuppressWarnings("unchecked")
    private static void addWebPersistenceDD(final String name, final Map<String, Object> otherDD, final AppModule appModule) {
        if (otherDD.containsKey(name)) {
            List<URL> persistenceUrls = (List<URL>) appModule.getAltDDs().get(name);
            if (persistenceUrls == null) {
                persistenceUrls = new ArrayList<>();
                appModule.getAltDDs().put(name, persistenceUrls);
            }

            if (otherDD.containsKey(name)) {
                final Object otherUrl = otherDD.get(name);
                if (otherUrl instanceof URL && !persistenceUrls.contains(otherUrl)) {
                    persistenceUrls.add((URL) otherUrl);
                } else if (otherUrl instanceof List) {
                    final List<URL> otherList = (List<URL>) otherDD.get(name);
                    for (final URL url : otherList) {
                        if (!persistenceUrls.contains(url)) {
                            persistenceUrls.add(url);
                        }
                    }
                }
            }
        }
    }

    protected AppModule createAppModule(final File jarFile, final String jarPath) throws OpenEJBException {
        File appDir = unpack(jarFile);
        try {
            appDir = appDir.getCanonicalFile();
        } catch (final IOException e) {
            throw new OpenEJBException("Invalid application directory " + appDir.getAbsolutePath());
        }

        final URL appUrl = getFileUrl(appDir);

        final String appId = appDir.getAbsolutePath();
        final ClassLoader tmpClassLoader = ClassLoaderUtil.createTempClassLoader(appId, new URL[]{appUrl}, getOpenEJBClassLoader());

        final ResourceFinder finder = new ResourceFinder("", tmpClassLoader, appUrl);
        final Map<String, URL> appDescriptors = getDescriptors(finder);

        try {

            //
            // Find all the modules using either the application xml or by searching for all .jar, .war and .rar files.
            //

            final Map<String, URL> ejbModules = new LinkedHashMap<>();
            final Map<String, URL> clientModules = new LinkedHashMap<>();
            final Map<String, URL> resouceModules = new LinkedHashMap<>();
            final Map<String, URL> webModules = new LinkedHashMap<>();
            final Map<String, String> webContextRoots = new LinkedHashMap<>();

            final URL applicationXmlUrl = appDescriptors.get("application.xml");
            final List<URL> extraLibs = new ArrayList<>();

            final Application application;
            if (applicationXmlUrl != null) {

                application = unmarshal(applicationXmlUrl);
                for (final Module module : application.getModule()) {
                    try {
                        if (module.getEjb() != null) {
                            final URL url = finder.find(module.getEjb().trim());
                            ejbModules.put(module.getEjb(), url);
                        } else if (module.getJava() != null) {
                            final URL url = finder.find(module.getJava().trim());
                            clientModules.put(module.getJava(), url);
                            extraLibs.add(url);
                        } else if (module.getConnector() != null) {
                            final URL url = finder.find(module.getConnector().trim());
                            resouceModules.put(module.getConnector(), url);
                        } else if (module.getWeb() != null) {
                            final URL url = finder.find(module.getWeb().getWebUri().trim());
                            webModules.put(module.getWeb().getWebUri(), url);
                            webContextRoots.put(module.getWeb().getWebUri(), module.getWeb().getContextRoot());
                        }
                    } catch (final IOException e) {
                        throw new OpenEJBException("Invalid path to module " + e.getMessage(), e);
                    }
                }
            } else {
                application = new Application();
                final HashMap<String, URL> files = new HashMap<>();
                scanDir(appDir, files, "", false);
                files.remove("META-INF/MANIFEST.MF");

                // todo we should also filter URLs here using DeploymentsResolver.loadFromClasspath

                createApplicationFromFiles(appId, tmpClassLoader, ejbModules, clientModules, resouceModules, webModules, files);
            }

            final ClassLoaderConfigurer configurer = QuickJarsTxtParser.parse(new File(appDir, "META-INF/" + QuickJarsTxtParser.FILE_NAME));
            final Collection<URL> jarsXmlLib = new ArrayList<>();
            if (configurer != null) {
                for (final URL url : configurer.additionalURLs()) {
                    try {
                        detectAndAddModuleToApplication(appId, tmpClassLoader,
                            ejbModules, clientModules, resouceModules, webModules,
                            new ImmutablePair<>(URLs.toFile(url).getAbsolutePath(), url));
                    } catch (final Exception e) {
                        jarsXmlLib.add(url);
                    }
                }
            }

            //
            // Create a class loader for the application
            //

            // lib/*
            if (application.getLibraryDirectory() == null) {
                application.setLibraryDirectory("lib/");
            } else {
                final String dir = application.getLibraryDirectory();
                if (!dir.endsWith("/")) {
                    application.setLibraryDirectory(dir + "/");
                }
            }

            try {
                final Map<String, URL> libs = finder.getResourcesMap(application.getLibraryDirectory());
                extraLibs.addAll(libs.values());
            } catch (final IOException e) {
                LOGGER.warning("Cannot load libs from '" + application.getLibraryDirectory() + "' : " + e.getMessage(), e);
            }

            // APP-INF/lib/*
            try {
                final Map<String, URL> libs = finder.getResourcesMap("APP-INF/lib/");
                extraLibs.addAll(libs.values());
            } catch (final IOException e) {
                LOGGER.warning("Cannot load libs from 'APP-INF/lib/' : " + e.getMessage(), e);
            }

            // META-INF/lib/*
            try {
                final Map<String, URL> libs = finder.getResourcesMap("META-INF/lib/");
                extraLibs.addAll(libs.values());
            } catch (final IOException e) {
                LOGGER.warning("Cannot load libs from 'META-INF/lib/' : " + e.getMessage(), e);
            }

            // All jars nested in the Resource Adapter
            final HashMap<String, URL> rarLibs = new HashMap<>();
            for (final Map.Entry<String, URL> entry : resouceModules.entrySet()) {
                try {
                    // unpack the resource adapter archive
                    File rarFile = URLs.toFile(entry.getValue());
                    rarFile = unpack(rarFile);
                    entry.setValue(rarFile.toURI().toURL());

                    scanDir(appDir, rarLibs, "");
                } catch (final MalformedURLException e) {
                    throw new OpenEJBException("Malformed URL to app. " + e.getMessage(), e);
                }
            }
            for (final Iterator<Map.Entry<String, URL>> iterator = rarLibs.entrySet().iterator(); iterator.hasNext(); ) {
                // remove all non jars from the rarLibs
                final Map.Entry<String, URL> fileEntry = iterator.next();
                if (!fileEntry.getKey().endsWith(".jar")) {
                    continue;
                }
                iterator.remove();
            }

            final List<URL> classPath = new ArrayList<>();
            classPath.addAll(ejbModules.values());
            classPath.addAll(clientModules.values());
            classPath.addAll(rarLibs.values());
            classPath.addAll(extraLibs);
            classPath.addAll(jarsXmlLib);
            final URL[] urls = classPath.toArray(new URL[classPath.size()]);

            SystemInstance.get().fireEvent(new BeforeDeploymentEvent(urls));

            final ClassLoader appClassLoader = ClassLoaderUtil.createTempClassLoader(appId, urls, getOpenEJBClassLoader());

            //
            // Create the AppModule and all nested module objects
            //

            final AppModule appModule = new AppModule(appClassLoader, appId, application, false);
            appModule.getAdditionalLibraries().addAll(extraLibs);
            appModule.getAltDDs().putAll(appDescriptors);
            appModule.getWatchedResources().add(appId);
            if (applicationXmlUrl != null) {
                appModule.getWatchedResources().add(URLs.toFilePath(applicationXmlUrl));
            }
            if (appDescriptors.containsKey(RESOURCES_XML)) {
                final Map<String, Object> altDd = new HashMap<>(appDescriptors);
                ReadDescriptors.readResourcesXml(new org.apache.openejb.config.Module(false) {
                    @Override
                    public Map<String, Object> getAltDDs() {
                        return altDd;
                    }

                    @Override
                    public void initResources(final Resources resources) {
                        appModule.getContainers().addAll(resources.getContainer());
                        appModule.getResources().addAll(resources.getResource());
                        appModule.getServices().addAll(resources.getService());
                    }
                });
            }

            // EJB modules
            for (final Map.Entry<String, URL> stringURLEntry : ejbModules.entrySet()) {
                try {
                    URL ejbUrl = stringURLEntry.getValue();
                    // we should try to use a reference to the temp classloader
                    if (ClassLoaderUtil.isUrlCached(appModule.getJarLocation(), ejbUrl)) {
                        try {
                            ejbUrl = ClassLoaderUtil.getUrlCachedName(appModule.getJarLocation(), ejbUrl).toURI().toURL();

                        } catch (final MalformedURLException ignore) {
                            // no-op
                        }
                    }
                    final File ejbFile = URLs.toFile(ejbUrl);
                    final String absolutePath = ejbFile.getAbsolutePath();

                    final EjbModule ejbModule = createEjbModule(ejbUrl, absolutePath, appClassLoader);
                    appModule.getEjbModules().add(ejbModule);
                } catch (final OpenEJBException e) {
                    LOGGER.error("Unable to load EJBs from EAR: " + appId + ", module: " + stringURLEntry.getKey() + ". Exception: " + e.getMessage(), e);
                }
            }

            // Application Client Modules
            for (final Map.Entry<String, URL> stringURLEntry : clientModules.entrySet()) {
                try {
                    URL clientUrl = stringURLEntry.getValue();
                    // we should try to use a reference to the temp classloader
                    if (ClassLoaderUtil.isUrlCached(appModule.getJarLocation(), clientUrl)) {
                        try {
                            clientUrl = ClassLoaderUtil.getUrlCachedName(appModule.getJarLocation(), clientUrl).toURI().toURL();

                        } catch (final MalformedURLException ignore) {
                            // no-op
                        }
                    }
                    final File clientFile = URLs.toFile(clientUrl);
                    final String absolutePath = clientFile.getAbsolutePath();

                    final ClientModule clientModule = createClientModule(clientUrl, absolutePath, appClassLoader, null);

                    appModule.getClientModules().add(clientModule);
                } catch (final Exception e) {
                    LOGGER.error("Unable to load App Client from EAR: " + appId + ", module: " + stringURLEntry.getKey() + ". Exception: " + e.getMessage(), e);
                }
            }

            // Resource modules
            for (final Map.Entry<String, URL> stringURLEntry : resouceModules.entrySet()) {
                try {
                    URL rarUrl = stringURLEntry.getValue();
                    // we should try to use a reference to the temp classloader
                    if (ClassLoaderUtil.isUrlCached(appModule.getJarLocation(), rarUrl)) {
                        try {
                            rarUrl = ClassLoaderUtil.getUrlCachedName(appModule.getJarLocation(), rarUrl).toURI().toURL();

                        } catch (final MalformedURLException ignore) {
                            // no-op
                        }
                    }
                    final ConnectorModule connectorModule = createConnectorModule(appId, URLs.toFilePath(rarUrl), appClassLoader, stringURLEntry.getKey());
                    if (connectorModule != null) {
                        appModule.getConnectorModules().add(connectorModule);
                    }
                } catch (final OpenEJBException e) {
                    LOGGER.error("Unable to load RAR: " + appId + ", module: " + stringURLEntry.getKey() + ". Exception: " + e.getMessage(), e);
                }
            }

            // Web modules
            for (final Map.Entry<String, URL> stringURLEntry : webModules.entrySet()) {
                try {
                    final URL warUrl = stringURLEntry.getValue();
                    addWebModule(appModule, warUrl, appClassLoader, webContextRoots.get(stringURLEntry.getKey()), null);
                } catch (final OpenEJBException e) {
                    LOGGER.error("Unable to load WAR: " + appId + ", module: " + stringURLEntry.getKey() + ". Exception: " + e.getMessage(), e);
                }
            }


            addBeansXmls(appModule);

            // Persistence Units
            final Properties p = new Properties();
            p.put(appModule.getModuleId(), appModule.getJarLocation());
            final FileUtils base = new FileUtils(appModule.getModuleId(), appModule.getModuleId(), p);
            final List<URL> filteredUrls = new ArrayList<>();
            DeploymentsResolver.loadFromClasspath(base, filteredUrls, appModule.getClassLoader());
            addPersistenceUnits(appModule, filteredUrls.toArray(new URL[filteredUrls.size()]));

            final Object pXmls = appModule.getAltDDs().get("persistence.xml");

            for (final WebModule webModule : appModule.getWebModules()) {
                final List<URL> foundRootUrls = new ArrayList<>();
                final List<URL> scannableUrls = webModule.getScannableUrls();
                for (final URL url : scannableUrls) {
                    if (!addPersistenceUnits(appModule, url).isEmpty()) {
                        foundRootUrls.add(url);
                    }
                }

                if (pXmls != null && Collection.class.isInstance(pXmls)) {
                    final File webapp = webModule.getFile();
                    if (webapp == null) {
                        continue;
                    }
                    final String webappAbsolutePath = webapp.getAbsolutePath();

                    final Collection<URL> list = Collection.class.cast(pXmls);
                    for (final URL url : list) {
                        try {
                            final File file = URLs.toFile(url);
                            if (file.getAbsolutePath().startsWith(webappAbsolutePath)) {
                                foundRootUrls.add(url);
                            }
                        } catch (final IllegalArgumentException iae) {
                            // no-op
                        }
                    }
                }

                webModule.getAltDDs().put(EAR_WEBAPP_PERSISTENCE_XML_JARS, foundRootUrls);
            }

            for (final DeploymentModule module : appModule.getDeploymentModule()) {
                module.setStandaloneModule(false);
            }

            return appModule;

        } catch (final OpenEJBException e) {
            LOGGER.error("Unable to load EAR: " + jarPath, e);
            throw e;
        }
    }

    private void createApplicationFromFiles(final String appId, final ClassLoader tmpClassLoader, final Map<String, URL> ejbModules, final Map<String, URL> clientModules, final Map<String, URL> resouceModules, final Map<String, URL> webModules, final HashMap<String, URL> files) throws OpenEJBException {
        for (final Map.Entry<String, URL> entry : files.entrySet()) {
            // if (entry.getKey().startsWith("lib/")) continue;// will not be scanned since we don't get folder anymore
            if (!entry.getKey().matches(".*\\.(jar|war|rar|ear)")) {
                continue;
            }

            try {
                detectAndAddModuleToApplication(appId, tmpClassLoader, ejbModules, clientModules, resouceModules, webModules, entry);
            } catch (final UnsupportedOperationException | UnknownModuleTypeException e) {
                // Ignore it as per the javaee spec EE.8.4.2 section 1.d.iii
                LOGGER.info("Ignoring unknown module type: " + entry.getKey());
            } catch (final Exception e) {
                throw new OpenEJBException("Unable to determine the module type of " + entry.getKey() + ": Exception: " + e.getMessage(), e);
            }
        }
    }

    private void detectAndAddModuleToApplication(final String appId, final ClassLoader tmpClassLoader, final Map<String, URL> ejbModules, final Map<String, URL> clientModules, final Map<String, URL> resouceModules, final Map<String, URL> webModules, final Map.Entry<String, URL> entry) throws IOException, UnknownModuleTypeException {
        final ClassLoader moduleClassLoader = ClassLoaderUtil.createTempClassLoader(appId, new URL[]{entry.getValue()}, tmpClassLoader);

        final Class<? extends DeploymentModule> moduleType = discoverModuleType(entry.getValue(), moduleClassLoader, true);

        if (EjbModule.class.equals(moduleType)) {
            ejbModules.put(entry.getKey(), entry.getValue());
        } else if (ClientModule.class.equals(moduleType)) {
            clientModules.put(entry.getKey(), entry.getValue());
        } else if (ConnectorModule.class.equals(moduleType)) {
            resouceModules.put(entry.getKey(), entry.getValue());
        } else if (WebModule.class.equals(moduleType)) {
            webModules.put(entry.getKey(), entry.getValue());
        }
    }

    protected ClientModule createClientModule(final URL clientUrl, final String absolutePath, final ClassLoader appClassLoader, final String moduleName) throws OpenEJBException {
        return createClientModule(clientUrl, absolutePath, appClassLoader, moduleName, true);
    }

    protected ClientModule createClientModule(final URL clientUrl, final String absolutePath, final ClassLoader appClassLoader, final String moduleName, final boolean log) throws OpenEJBException {
        final ResourceFinder clientFinder = new ResourceFinder(clientUrl);

        URL manifestUrl = null;
        try {
            manifestUrl = clientFinder.find("META-INF/MANIFEST.MF");
        } catch (final IOException e) {
            //
        }

        String mainClass = null;
        if (manifestUrl != null) {
            try {
                final InputStream is = IO.read(manifestUrl);
                final Manifest manifest = new Manifest(is);
                mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
            } catch (final IOException e) {
                throw new OpenEJBException("Unable to determine Main-Class defined in META-INF/MANIFEST.MF file", e);
            }
        }

//        if (mainClass == null) throw new IllegalStateException("No Main-Class defined in META-INF/MANIFEST.MF file");

        final Map<String, URL> descriptors = getDescriptors(clientFinder, log);

        ApplicationClient applicationClient = null;
        final URL clientXmlUrl = descriptors.get("application-client.xml");
        if (clientXmlUrl != null) {
            applicationClient = ReadDescriptors.readApplicationClient(clientXmlUrl);
        }

        final ClientModule clientModule = new ClientModule(applicationClient, appClassLoader, absolutePath, mainClass, moduleName);

        clientModule.getAltDDs().putAll(descriptors);
        if (absolutePath != null) {
            clientModule.getWatchedResources().add(absolutePath);
        }
        if (clientXmlUrl != null && "file".equals(clientXmlUrl.getProtocol())) {
            clientModule.getWatchedResources().add(URLs.toFilePath(clientXmlUrl));
        }
        return clientModule;
    }

    protected EjbModule createEjbModule(final URL baseUrl, final String jarPath, final ClassLoader classLoader) throws OpenEJBException {
        // read the ejb-jar.xml file
        Map<String, URL> descriptors;
        if (baseUrl != null) {
            descriptors = getDescriptors(baseUrl);
        } else {
            try {
                descriptors = getDescriptors(classLoader, null);
            } catch (final IOException e) {
                descriptors = new HashMap<>();
            }
        }

        EjbJar ejbJar = null;
        final URL ejbJarXmlUrl = descriptors.get("ejb-jar.xml");
        if (ejbJarXmlUrl != null) {
            try {
                ejbJar = ReadDescriptors.readEjbJar(ejbJarXmlUrl.openStream());
            } catch (final IOException e) {
                throw new OpenEJBException(e);
            }
        }

        // create the EJB Module
        final EjbModule ejbModule = new EjbModule(classLoader, null, jarPath, ejbJar, null);
        ejbModule.getAltDDs().putAll(descriptors);
        if (jarPath != null) {
            ejbModule.getWatchedResources().add(jarPath);
        }
        if (ejbJarXmlUrl != null && "file".equals(ejbJarXmlUrl.getProtocol())) {
            ejbModule.getWatchedResources().add(URLs.toFilePath(ejbJarXmlUrl));
        }

        ejbModule.setClientModule(createClientModule(baseUrl, jarPath, classLoader, null, false));

        // load webservices descriptor
        addWebservices(ejbModule);
        return ejbModule;
    }

    private WebModule createWebModule(final String jar, final URL warUrl, final ClassLoader parentClassLoader, final String contextRoot, final String moduleName, final ExternalConfiguration config) throws OpenEJBException {
        return createWebModule(jar, URLs.toFilePath(warUrl), parentClassLoader, contextRoot, moduleName, config);
    }

    public void addWebModule(final AppModule appModule, final URL warUrl, final ClassLoader parentClassLoader, final String contextRoot, final String moduleName) throws OpenEJBException {
        final WebModule webModule = createWebModule(appModule.getJarLocation(), URLs.toFilePath(warUrl), parentClassLoader, contextRoot, moduleName, null);
        addWebModule(webModule, appModule);
    }

    public static EjbModule addWebModule(final WebModule webModule, final AppModule appModule) throws OpenEJBException {
        // create and add the WebModule
        appModule.getWebModules().add(webModule);
        if (appModule.isStandaloneModule()) {
            appModule.getAdditionalLibraries().addAll(webModule.getUrls());
        }

        {
            final Object pXml = appModule.getAltDDs().get("persistence.xml");

            List<URL> persistenceXmls = pXml == null ? null : (List.class.isInstance(pXml) ? (List<URL>) pXml :
                    new ArrayList<>(Collections.singletonList(URL.class.cast(pXml))));
            if (persistenceXmls == null) {
                persistenceXmls = new ArrayList<>();
                appModule.getAltDDs().put("persistence.xml", persistenceXmls);
            }

            final Object o = webModule.getAltDDs().get("persistence.xml");

            if (o instanceof URL) {
                final URL url = (URL) o;
                persistenceXmls.add(url);
            }

            if (o instanceof List) {
                final List<URL> urls = (List<URL>) o;
                persistenceXmls.addAll(urls);
            }
        }


        // Per the Spec version of the Collapsed EAR there
        // aren't individual EjbModules inside a war.
        // The war itself is one big EjbModule if certain
        // conditions are met.  These conditions are different
        // than an ear file, so the ear-style code we were previously
        // using doesn't exactly work anymore.
        final EjbModule webEjbModule = new EjbModule(webModule.getClassLoader(), webModule.getModuleId(), webModule.getJarLocation(), null, null);
        webEjbModule.setWebapp(true);
        webEjbModule.getAltDDs().putAll(webModule.getAltDDs());
        appModule.getEjbModules().add(webEjbModule);

        try {
            // TODO:  Put our scanning ehnancements back, here
            fillEjbJar(webModule, webEjbModule);

            if (webModule.getFinder() == null) {
                if (isMetadataComplete(webModule, webEjbModule)) {
                    final IAnnotationFinder finder = new org.apache.xbean.finder.AnnotationFinder(new ClassesArchive());
                    webModule.setFinder(finder);
                    webEjbModule.setFinder(finder);
                } else {
                    final IAnnotationFinder finder = FinderFactory.createFinder(webModule);
                    webModule.setFinder(finder);
                    webEjbModule.setFinder(finder);
                }
            } else if (webEjbModule.getFinder() == null) {
                webEjbModule.setFinder(webModule.getFinder());
            }
        } catch (final Exception e) {
            throw new OpenEJBException("Unable to create annotation scanner for web module " + webModule.getModuleId(), e);
        }

        addWebservices(webEjbModule);

        return webEjbModule;
    }

    /**
     * If the web.xml is metadata-complete and there is no ejb-jar.xml
     * then per specification we use the web.xml metadata-complete setting
     * to imply the same for EJBs.
     *
     * @param webModule WebModule
     * @param ejbModule EjbModule
     */
    private static void fillEjbJar(final WebModule webModule, final EjbModule ejbModule) {
        final Object o = webModule.getAltDDs().get("ejb-jar.xml");
        if (o != null) {
            return;
        }
        if (ejbModule.getEjbJar() != null) {
            return;
        }

        final EjbJar ejbJar = new EjbJar();
        final WebApp webApp = webModule.getWebApp();

        ejbJar.setMetadataComplete(webApp.isMetadataComplete());

        ejbModule.setEjbJar(ejbJar);
    }

    private static boolean isMetadataComplete(final WebModule webModule, final EjbModule ejbModule) {
        if (webModule.getWebApp() == null) {
            return false;
        }
        if (!webModule.getWebApp().isMetadataComplete()) {
            return false;
        }

        // At this point we know the web.xml is metadata-complete
        // We need to determine if there are cdi or ejb xml files
        if (webModule.getAltDDs().get("beans.xml") == null) {
            return true;
        }
        if (ejbModule.getEjbJar() == null) {
            return true;
        }
        return ejbModule.getEjbJar().isMetadataComplete();

    }

    public WebModule createWebModule(final String appId, final String warPath, final ClassLoader parentClassLoader, final String contextRoot, final String moduleName, final ExternalConfiguration config) throws OpenEJBException {
        File warFile = new File(warPath);
        if (!warFile.isDirectory()) {
            warFile = unpack(warFile);
        }

        // read web.xml file
        final Map<String, URL> descriptors;
        try {
            descriptors = getWebDescriptors(warFile);
        } catch (final IOException e) {
            throw new OpenEJBException("Unable to collect descriptors in web module: " + contextRoot, e);
        }

        final WebApp webApp;
        final URL webXmlUrl = descriptors.get("web.xml");
        if (webXmlUrl != null) {
            webApp = ReadDescriptors.readWebApp(webXmlUrl);
        } else {
            // no web.xml webapp - possible since Servlet 3.0
            webApp = new WebApp();
        }

        // determine war class path

        ensureContainerUrls();
        final List<URL> webUrls = new ArrayList<>(containerUrls);

        final SystemInstance systemInstance = SystemInstance.get();

        // add these urls first to ensure we load classes from here first
        final String externalRepos = systemInstance.getProperty("tomee." + warFile.getName().replace(".war", "") + ".externalRepositories");
        List<URL> externalUrls = null;
        if (externalRepos != null) {
            externalUrls = new ArrayList<>();
            for (final String additional : externalRepos.split(",")) {
                final String trim = additional.trim();
                if (!trim.isEmpty()) {
                    try {
                        externalUrls.add(new File(trim).toURI().toURL());
                    } catch (final MalformedURLException e) {
                        LOGGER.error(e.getMessage());
                    }
                }
            }
            webUrls.addAll(externalUrls);
        }

        final Map<String, URL[]> urls = getWebappUrlsAndRars(warFile);
        webUrls.addAll(Arrays.asList(urls.get(URLS_KEY)));

        final List<URL> addedUrls = new ArrayList<>();
        for (final URL url : urls.get(RAR_URLS_KEY)) { // eager unpack to be able to use it in classloader
            final File[] files = unpack(URLs.toFile(url)).listFiles();
            if (files != null) {
                for (final File f : files) {
                    if (f.getName().endsWith(".jar")) {
                        try {
                            addedUrls.add(f.toURI().toURL());
                        } catch (final MalformedURLException e) {
                            LOGGER.warning("War path bad: " + f.getAbsolutePath(), e);
                        }
                    }
                }
            }
        }
        webUrls.addAll(addedUrls);

        // context.xml can define some additional libraries
        if (config != null) { // we don't test all !=null inline to show that config will get extra params in the future and that it is hierarchic
            if (config.getClasspath() != null && config.getClasspath().length > 0) {
                final Set<URL> contextXmlUrls = new LinkedHashSet<>();
                for (final String location : config.getClasspath()) {
                    try {
                        webUrls.add(new File(location).toURI().toURL());
                    } catch (final MalformedURLException e) {
                        throw new IllegalArgumentException(e);
                    }
                }
                webUrls.addAll(contextXmlUrls);
            }
        }

        final ClassLoaderConfigurer configurer = QuickJarsTxtParser.parse(new File(warFile, "WEB-INF/" + QuickJarsTxtParser.FILE_NAME));
        if (configurer != null) {
            ClassLoaderConfigurer.Helper.configure(webUrls, configurer);
        }

        final URL[] webUrlsArray = webUrls.toArray(new URL[webUrls.size()]);

        // in TomEE this is done in init hook since we don't manage tomee webapp classloader
        // so here is not the best idea for tomee
        // if we want to manage it in a generic way
        // simply add a boolean shared between tomcat and openejb world
        // to know if we should fire it or not
        systemInstance.fireEvent(new BeforeDeploymentEvent(webUrlsArray, parentClassLoader));

        final ClassLoader warClassLoader = ClassLoaderUtil.createTempClassLoader(appId, webUrlsArray, parentClassLoader);

        // create web module
        final List<URL> scannableUrls = filterWebappUrls(webUrlsArray, config == null ? null : config.customerFilter, descriptors.get(NewLoaderLogic.EXCLUSION_FILE));
        // executable war will add war in scannable urls, we don't want it since it will surely contain tomee, cxf, ...
        if (Boolean.parseBoolean(systemInstance.getProperty("openejb.core.skip-war-in-loader", "true"))) {
            File archive = warFile;
            if (!archive.getName().endsWith(".war")) {
                archive = new File(warFile.getParentFile(), warFile.getName() + ".war");
                final String unpackDir = systemInstance.getProperty("tomee.unpack.dir");
                if (unpackDir != null && !archive.isFile()) {
                    try {
                        archive = new File(systemInstance.getBase().getDirectory(unpackDir, false), warFile.getName());
                    } catch (final IOException e) {
                        // no-op
                    }
                }
            }
            if (archive.isFile()) {
                try {
                    scannableUrls.remove(archive.toURI().toURL());
                } catch (final MalformedURLException e) {
                    // no-op
                }
            }
        }
        if (externalUrls != null) {
            for (final URL url : externalUrls) {
                if (scannableUrls.contains(url)) {
                    scannableUrls.remove(url);
                    scannableUrls.add(0, url);
                }
            }
        }

        SystemInstance.get().fireEvent(new EnhanceScannableUrlsEvent(scannableUrls));

        final WebModule webModule = new WebModule(webApp, contextRoot, warClassLoader, warFile.getAbsolutePath(), moduleName);
        webModule.setUrls(webUrls);
        webModule.setAddedUrls(addedUrls);
        webModule.setRarUrls(Arrays.asList(urls.get(RAR_URLS_KEY)));
        webModule.setScannableUrls(scannableUrls);
        webModule.setDefaultContextPath(webApp.getDefaultContextPath());
        webModule.getAltDDs().putAll(descriptors);
        webModule.getWatchedResources().add(warPath);
        webModule.getWatchedResources().add(warFile.getAbsolutePath());
        if (webXmlUrl != null && "file".equals(webXmlUrl.getProtocol())) {
            webModule.getWatchedResources().add(URLs.toFilePath(webXmlUrl));
        }

        //If webModule object is loaded by ejbModule or persitenceModule, no need to load tag libraries, web service and JSF related staffs.
        addTagLibraries(webModule);

        // load webservices descriptor
        addWebservices(webModule);

        // load faces configuration files
        addFacesConfigs(webModule);

        addBeansXmls(webModule);

        return webModule;
    }

    private void ensureContainerUrls() {
        if (containerUrls == null) {
            if ("true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.scan.webapp.container", "false"))) {
                synchronized (this) {
                    if (containerUrls == null) {
                        try {
                            UrlSet urlSet = new UrlSet(ParentClassLoaderFinder.Helper.get());
                            urlSet = URLs.cullSystemJars(urlSet);
                            urlSet = NewLoaderLogic.applyBuiltinExcludes(urlSet);
                            containerUrls = urlSet.getUrls();

                            final boolean skipContainerFolders = "true".equalsIgnoreCase(SystemInstance.get().getProperty("openejb.scan.webapp.container.skip-folder", "true"));
                            final Iterator<URL> it = containerUrls.iterator();
                            while (it.hasNext()) { // remove lib/
                                final File file = URLs.toFile(it.next());
                                // TODO: see if websocket should be added in default.exclusions
                                final String name = file.getName();
                                if ((skipContainerFolders && file.isDirectory())
                                        // few hardcoded exclusions, TODO: see if we should filter them in previous call of applyBuiltinExcludes()
                                        || name.endsWith("tomcat-websocket.jar")
                                        || name.startsWith("commons-jcs-")
                                        || name.startsWith("xx-arquillian-tomee")
                                        || ("lib".equals(name) && file.isDirectory() &&
                                            new File(JavaSecurityManagers.getSystemProperty("openejb.base", "-")).equals(file.getParentFile()))) {
                                    it.remove();
                                }
                            }
                        } catch (final Exception e) {
                            LOGGER.error(e.getMessage(), e);
                        }
                    }
                }
            } else {
                containerUrls = new ArrayList<>();
            }
        }
    }

    public static List<URL> filterWebappUrls(final URL[] webUrls, final Filter filter, final URL exclusions) {
        Filter excludeFilter = null;
        if (exclusions != null) {
            try {
                final String[] prefixes = NewLoaderLogic.readInputStreamList(exclusions.openStream());
                excludeFilter = Filters.prefixes(prefixes);
            } catch (final IOException e) {
                LOGGER.warning("can't read " + exclusions.toExternalForm());
            }
        }

        UrlSet urls = new UrlSet(webUrls);
        try {
            urls = NewLoaderLogic.applyBuiltinExcludes(urls, filter, excludeFilter);
        } catch (final MalformedURLException e) {
            return Arrays.asList(webUrls);
        }
        return urls.getUrls();
    }

    public static void addBeansXmls(final WebModule webModule) {
        final List<URL> urls = webModule.getScannableUrls();
        // parent returns nothing when calling getresources because we don't want here to be fooled by maven classloader
        final URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[urls.size()]), new EmptyResourcesClassLoader());

        final List<URL> xmls = new LinkedList<>();
        try {
            final URL e = (URL) webModule.getAltDDs().get("beans.xml");
            if (e != null) { // first!
                xmls.add(e);
            }
            xmls.addAll(Collections.list(loader.getResources("META-INF/beans.xml")));
        } catch (final IOException e) {
            return;
        }

        final CompositeBeans complete = new CompositeBeans();
        for (final URL url : xmls) {
            if (url == null) {
                continue;
            }
            mergeBeansXml(complete, url);
        }
        if (!complete.getDiscoveryByUrl().isEmpty()) {
            complete.removeDuplicates();
        }
        webModule.getAltDDs().put("beans.xml", complete);
    }

    private static Beans mergeBeansXml(final CompositeBeans current, final URL url) {
        try {
            final Beans beans;
            try {
                beans = ReadDescriptors.readBeans(url.openStream());
            } catch (final IOException e) {
                return current;
            }

            doMerge(url, current, beans);
        } catch (final OpenEJBException e) {
            LOGGER.error("Unable to read beans.xml from: " + url.toExternalForm(), e);
        }
        return current;
    }

    public static void doMerge(final URL url, final CompositeBeans current, final Beans beans) {
        current.mergeClasses(url, beans);
        current.getScan().getExclude().addAll(beans.getScan().getExclude());

        // check is done here since later we lost the data of the origin
        ReadDescriptors.checkDuplicatedByBeansXml(beans, current);

        String beanDiscoveryMode = beans.getBeanDiscoveryMode();
        if (beanDiscoveryMode == null) {
            beanDiscoveryMode = "ALL";
        }
        else if ("ALL".equalsIgnoreCase(beanDiscoveryMode) && beans.isTrim()) {
            beanDiscoveryMode = "TRIM";
        }

        current.getDiscoveryByUrl().put(url, beanDiscoveryMode);
    }

    private void addBeansXmls(final AppModule appModule) {
        final List<URL> urls = appModule.getAdditionalLibraries();
        final URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[urls.size()]));

        final ArrayList<URL> xmls;
        try {
            xmls = Collections.list(loader.getResources("META-INF/beans.xml"));
        } catch (final IOException e) {

            return;
        }

        final CompositeBeans complete = new CompositeBeans();
        for (final URL url : xmls) {
            if (url == null) {
                continue;
            }
            mergeBeansXml(complete, url);
        }

        if (complete.getDiscoveryByUrl().isEmpty()) {
            return;
        }
        complete.removeDuplicates();

        ensureContainerUrls();
        final List<URL> scannableUrls = new ArrayList<>(this.containerUrls);
        SystemInstance.get().fireEvent(new EnhanceScannableUrlsEvent(scannableUrls));
        appModule.getScannableContainerUrls().addAll(scannableUrls);

        IAnnotationFinder finder;
        try {
            finder = FinderFactory.createFinder(appModule);
        } catch (final Exception e) {
            finder = new FinderFactory.ModuleLimitedFinder(new FinderFactory.OpenEJBAnnotationFinder(new WebappAggregatedArchive(appModule.getClassLoader(), appModule.getAltDDs(), xmls)));
        }
        appModule.setEarLibFinder(finder);

        final EjbModule ejbModule = new EjbModule(appModule.getClassLoader(), EAR_SCOPED_CDI_BEANS + appModule.getModuleId(), new EjbJar(), new OpenejbJar());
        ejbModule.setBeans(complete);
        ejbModule.setFinder(finder);
        ejbModule.setEjbJar(new EmptyEjbJar());

        appModule.getEjbModules().add(ejbModule);
    }

    protected String getContextRoot() {
        return null;
    }

    protected String getModuleName() {
        return null;
    }

    public static Map<String, URL[]> getWebappUrlsAndRars(final File warFile) {
        final Set<URL> webClassPath = new HashSet<>();
        final Set<URL> webRars = new HashSet<>();
        final File webInfDir = new File(warFile, "WEB-INF");
        try {
            webClassPath.add(new File(webInfDir, "classes").toURI().toURL());
        } catch (final MalformedURLException e) {
            LOGGER.warning("War path bad: " + new File(webInfDir, "classes"), e);
        }

        final File libDir = new File(webInfDir, "lib");
        if (libDir.exists()) {
            final File[] list = libDir.listFiles();
            if (list != null) {
                for (final File file : list) {
                    final String name = file.getName();
                    if (name.endsWith(".jar") || name.endsWith(".zip")) {
                        try {
                            webClassPath.add(file.toURI().toURL());
                        } catch (final MalformedURLException e) {
                            LOGGER.warning("War path bad: " + file, e);
                        }
                    } else if (name.endsWith(".rar")) {
                        try {
                            webRars.add(file.toURI().toURL());
                        } catch (final MalformedURLException e) {
                            LOGGER.warning("War path bad: " + file, e);
                        }
                    }
                }
            }
        }

        final WebAppEnricher enricher = SystemInstance.get().getComponent(WebAppEnricher.class);
        if (enricher != null) {
            webClassPath.addAll(Arrays.asList(enricher.enrichment(null)));
        }

        // create the class loader
        final Map<String, URL[]> urls = new HashMap<>();
        urls.put(URLS_KEY, webClassPath.toArray(new URL[webClassPath.size()]));
        urls.put(RAR_URLS_KEY, webRars.toArray(new URL[webRars.size()]));
        return urls;
    }

    public static URL[] getWebappUrls(final File warFile) {
        return getWebappUrlsAndRars(warFile).get("urls");
    }

    private static void addWebservices(final WsModule wsModule) throws OpenEJBException {
        final boolean webservicesEnabled = SystemInstance.get().getOptions().get(ConfigurationFactory.WEBSERVICES_ENABLED, true);
        if (!webservicesEnabled) {
            wsModule.getAltDDs().remove("webservices.xml");
            wsModule.setWebservices(null); // should be null already, but just for good measure
            return;
        }

        // get location of webservices.xml file
        final Object webservicesObject = wsModule.getAltDDs().get("webservices.xml");
        if (webservicesObject == null || !(webservicesObject instanceof URL)) {
            return;
        }
        final URL webservicesUrl = (URL) webservicesObject;

        // determine the base url for this module (either file: or jar:)
        URL moduleUrl;
        try {
            final File jarFile = new File(wsModule.getJarLocation());
            moduleUrl = jarFile.toURI().toURL();
            if (jarFile.isFile()) {
                moduleUrl = new URL("jar", "", -1, moduleUrl + "!/");
            }
        } catch (final MalformedURLException e) {
            LOGGER.warning("Invalid module location " + wsModule.getJarLocation());
            return;
        }

        // parse the webservices.xml file
        final Map<URL, JavaWsdlMapping> jaxrpcMappingCache = new HashMap<>();
        final Webservices webservices = ReadDescriptors.readWebservices(webservicesUrl);
        wsModule.setWebservices(webservices);
        if ("file".equals(webservicesUrl.getProtocol())) {
            wsModule.getWatchedResources().add(URLs.toFilePath(webservicesUrl));
        }

        // parse any jaxrpc-mapping-files mentioned in the webservices.xml file
        for (final WebserviceDescription webserviceDescription : webservices.getWebserviceDescription()) {
            final String jaxrpcMappingFile = webserviceDescription.getJaxrpcMappingFile();
            if (jaxrpcMappingFile != null) {
                final URL jaxrpcMappingUrl;
                try {
                    jaxrpcMappingUrl = new URL(moduleUrl, jaxrpcMappingFile);
                    JavaWsdlMapping jaxrpcMapping = jaxrpcMappingCache.get(jaxrpcMappingUrl);
                    if (jaxrpcMapping == null) {
                        jaxrpcMapping = ReadDescriptors.readJaxrpcMapping(jaxrpcMappingUrl);
                        jaxrpcMappingCache.put(jaxrpcMappingUrl, jaxrpcMapping);
                    }
                    webserviceDescription.setJaxrpcMapping(jaxrpcMapping);
                    if ("file".equals(jaxrpcMappingUrl.getProtocol())) {
                        wsModule.getWatchedResources().add(URLs.toFilePath(jaxrpcMappingUrl));
                    }
                } catch (final MalformedURLException e) {
                    LOGGER.warning("Invalid jaxrpc-mapping-file location " + jaxrpcMappingFile);
                }
            }
        }

    }

    private void addTagLibraries(final WebModule webModule) throws OpenEJBException {
        final Set<URL> tldLocations = new HashSet<>();

        // web.xml contains tag lib locations in nested jsp config elements
        final File warFile = new File(webModule.getJarLocation());
        final WebApp webApp = webModule.getWebApp();
        if (webApp != null) {
            for (final JspConfig jspConfig : webApp.getJspConfig()) {
                for (final Taglib taglib : jspConfig.getTaglib()) {
                    String location = taglib.getTaglibLocation();
                    if (!location.startsWith("/")) {
                        // this reproduces a tomcat bug
                        location = "/WEB-INF/" + location;
                    }
                    try {
                        final File file = new File(warFile, location).getCanonicalFile().getAbsoluteFile();
                        tldLocations.addAll(TldScanner.scanForTagLibs(file));
                    } catch (final IOException e) {
                        LOGGER.warning("JSP tag library location bad: " + location, e);
                    }
                }
            }
        }

        // WEB-INF/**/*.tld except in WEB-INF/classes and WEB-INF/lib
        Set<URL> urls = TldScanner.scanWarForTagLibs(warFile);
        tldLocations.addAll(urls);

        // Search all libs
        final ClassLoader parentClassLoader = webModule.getClassLoader().getParent();
        urls = TldScanner.scan(parentClassLoader);
        tldLocations.addAll(urls);

        // load the tld files
        for (final URL location : tldLocations) {
            final TldTaglib taglib = ReadDescriptors.readTldTaglib(location);
            if (taglib != null && taglib != ReadDescriptors.SKIP_TAGLIB) {
                webModule.getTaglibs().add(taglib);
                if ("file".equals(location.getProtocol())) {
                    webModule.getWatchedResources().add(URLs.toFilePath(location));
                }
            }
        }

        // no more need + this classloader is a temp one in Servlet container so avoid mem leaks
        TldScanner.quickClean(parentClassLoader);
    }

    /**
     * Finds all faces configuration files and stores them in the WebModule
     *
     * @param webModule WebModule
     * @throws OpenEJBException
     */
    private void addFacesConfigs(final WebModule webModule) throws OpenEJBException {
        //*************************IMPORTANT*******************************************
        // TODO : kmalhi :: Add support to scrape META-INF/faces-config.xml in jar files
        // look at section 10.4.2 of the JSF v1.2 spec, bullet 1 for details
        final Set<URL> facesConfigLocations = new HashSet<>();

        // web.xml contains faces config locations in the context parameter javax.faces.CONFIG_FILES
        final File warFile = new File(webModule.getJarLocation());
        final WebApp webApp = webModule.getWebApp();
        if (webApp != null) {
            final String foundContextParam = webApp.contextParamsAsMap().get("javax.faces.CONFIG_FILES");
            if (foundContextParam != null) {
                // the value is a comma separated list of config files
                final String commaDelimitedListOfFiles = foundContextParam.trim();
                final String[] configFiles = commaDelimitedListOfFiles.split(",");
                // trim any extra spaces in each file
                final String[] trimmedConfigFiles = new String[configFiles.length];
                for (int i = 0; i < configFiles.length; i++) {
                    trimmedConfigFiles[i] = configFiles[i].trim();
                }
                // convert each file to a URL and add it to facesConfigLocations
                for (final String location : trimmedConfigFiles) {
                    if (!location.startsWith("/")) {
                        LOGGER.error("A faces configuration file should be context relative when specified in web.xml. Please fix the value of context parameter javax.faces.CONFIG_FILES for the file " + location);
                    }
                    try {
                        final File file = new File(warFile, location).getCanonicalFile().getAbsoluteFile();
                        final URL url = file.toURI().toURL();
                        facesConfigLocations.add(url);

                    } catch (final IOException e) {
                        LOGGER.error("Faces configuration file location bad: " + location, e);
                    }
                }
            } else {
                LOGGER.debug("faces config file is null");
            }
        }

        // Search for WEB-INF/faces-config.xml
        final File webInf = new File(warFile, "WEB-INF");
        if (webInf.isDirectory()) {
            File facesConfigFile = new File(webInf, "faces-config.xml");
            if (facesConfigFile.exists()) {
                try {
                    facesConfigFile = facesConfigFile.getCanonicalFile().getAbsoluteFile();
                    final URL url = facesConfigFile.toURI().toURL();
                    facesConfigLocations.add(url);
                } catch (final IOException e) {
                    // TODO: kmalhi:: Remove the printStackTrace after testing
                    e.printStackTrace();
                }
            }
        }
        // load the faces configuration files
        // TODO:kmalhi:: Its good to have separate FacesConfig objects for multiple configuration files, but what if there is a conflict where the same
        // managebean is declared in two different files, which one wins? -- check the jsf spec, Hopefully JSF should be able to check for this and
        // flag an error and not allow the application to be deployed.
        for (final URL location : facesConfigLocations) {
            final FacesConfig facesConfig = ReadDescriptors.readFacesConfig(location);
            webModule.getFacesConfigs().add(facesConfig);
            if ("file".equals(location.getProtocol())) {
                webModule.getWatchedResources().add(URLs.toFilePath(location));
            }
        }
    }

    protected static ConnectorModule createConnectorModule(final String appId, final String rarPath, final ClassLoader parentClassLoader, final String moduleId) throws OpenEJBException {
        return createConnectorModule(appId, rarPath, parentClassLoader, moduleId, null);
    }

    protected static ConnectorModule createConnectorModule(final String appId, final String rarPath, final ClassLoader parentClassLoader, final String moduleId, final URL raXmlUrl) throws OpenEJBException {
        final URL baseUrl;// unpack the rar file
        File rarFile = new File(rarPath);
        if (!rarFile.exists()) {
            LOGGER.warning(rarPath + " doesn't exist, skipping connector");
            return null;
        }
        rarFile = unpack(rarFile);
        baseUrl = getFileUrl(rarFile);

        // read the ra.xml file
        final Map<String, URL> descriptors = getDescriptors(baseUrl);
        Connector connector = null;
        URL rarXmlUrl = descriptors.get("ra.xml");
        if (rarXmlUrl == null && raXmlUrl != null) {
            descriptors.put("ra.xml", raXmlUrl);
            rarXmlUrl = raXmlUrl;
        }
        if (rarXmlUrl != null) {
            connector = ReadDescriptors.readConnector(rarXmlUrl);
        }

        // find the nested jar files
        final HashMap<String, URL> rarLibs = new HashMap<>();
        scanDir(rarFile, rarLibs, "");
        // remove all non jars from the rarLibs
        rarLibs.entrySet().removeIf(fileEntry -> !fileEntry.getKey().endsWith(".jar"));

        // create the class loader
        final List<URL> classPath = new ArrayList<>(rarLibs.values());

        final ClassLoaderConfigurer configurer = QuickJarsTxtParser.parse(new File(rarFile, "META-INF/" + QuickJarsTxtParser.FILE_NAME));
        if (configurer != null) {
            ClassLoaderConfigurer.Helper.configure(classPath, configurer);
        }

        final URL[] urls = classPath.toArray(new URL[classPath.size()]);
        final ClassLoader appClassLoader = ClassLoaderUtil.createTempClassLoader(appId, urls, parentClassLoader);

        // create the Resource Module
        final ConnectorModule connectorModule = new ConnectorModule(connector, appClassLoader, rarPath, moduleId);
        connectorModule.getAltDDs().putAll(descriptors);
        connectorModule.getLibraries().addAll(classPath);
        connectorModule.getWatchedResources().add(rarPath);
        connectorModule.getWatchedResources().add(rarFile.getAbsolutePath());
        if (rarXmlUrl != null && "file".equals(rarXmlUrl.getProtocol())) {
            connectorModule.getWatchedResources().add(URLs.toFilePath(rarXmlUrl));
        }

        return connectorModule;
    }

    protected static void addWebFragments(final WebModule webModule, final Collection<URL> urls) throws OpenEJBException {
        if (urls == null) {
            return;
        }

        List<URL> webFragmentUrls;
        try {
            webFragmentUrls = (List<URL>) webModule.getAltDDs().get(WEB_FRAGMENT_XML);
        } catch (final ClassCastException e) {
            final Object value = webModule.getAltDDs().get(WEB_FRAGMENT_XML);
            webFragmentUrls = new ArrayList<>();
            webFragmentUrls.add(URL.class.cast(value));
            webModule.getAltDDs().put(WEB_FRAGMENT_XML, webFragmentUrls);
        }
        if (webFragmentUrls == null) {
            webFragmentUrls = new ArrayList<>();
            webModule.getAltDDs().put(WEB_FRAGMENT_XML, webFragmentUrls);
        }


        for (final URL url : urls) {
            final ResourceFinder finder = new ResourceFinder("", webModule.getClassLoader(), url);
            final Map<String, URL> descriptors = getDescriptors(finder, false);

            if (descriptors.containsKey(WEB_FRAGMENT_XML)) {
                final URL descriptor = descriptors.get(WEB_FRAGMENT_XML);
                if (webFragmentUrls.contains(descriptor)) {
                    continue;
                }

                final String urlString = descriptor.toExternalForm();
                if (!urlString.contains("META-INF/" + WEB_FRAGMENT_XML)) {
                    LOGGER.info("AltDD persistence.xml -> " + urlString);
                }

                webFragmentUrls.add(descriptor);
            }
        }
    }

    @SuppressWarnings({"unchecked"})
    protected static Collection<URL> addPersistenceUnits(final AppModule appModule, final URL... urls) throws OpenEJBException {
        final Collection<URL> added = new ArrayList<>();

        // OPENEJB-1059: Anything in the appModule.getAltDDs() map has already been
        // processed by the altdd code, so anything in here should not cause OPENEJB-1059
        List<URL> persistenceUrls;
        try {
            persistenceUrls = (List<URL>) appModule.getAltDDs().get("persistence.xml");
        } catch (final ClassCastException e) {
            //That happens when we are trying to deploy an ear file.
            //lets try to get a single object instead
            final Object value = appModule.getAltDDs().get("persistence.xml");

            persistenceUrls = new ArrayList<>();
            persistenceUrls.add(URL.class.cast(value));
            added.add(persistenceUrls.iterator().next());

            appModule.getAltDDs().put("persistence.xml", persistenceUrls);
        }
        if (persistenceUrls == null) {
            persistenceUrls = new ArrayList<>();
            appModule.getAltDDs().put("persistence.xml", persistenceUrls);
        }

        List<URL> persistenceFragmentsUrls = (List<URL>) appModule.getAltDDs().get("persistence-fragment.xml");
        if (persistenceFragmentsUrls == null) {
            persistenceFragmentsUrls = new ArrayList<>();
            appModule.getAltDDs().put("persistence-fragment.xml", persistenceFragmentsUrls);
        }


        for (final URL url : urls) {
            // OPENEJB-1059: looking for an altdd persistence.xml file in all urls
            // delegates to xbean finder for going throughout the list
            final ResourceFinder finder = new ResourceFinder("", appModule.getClassLoader(), url);
            final Map<String, URL> descriptors = getDescriptors(finder, false);

            // if a persistence.xml has been found, just pull it to the list
            if (descriptors.containsKey("persistence.xml")) {
                final URL descriptor = descriptors.get("persistence.xml");

                // don't add it if already present
                if (persistenceUrls.contains(descriptor)) {
                    continue;
                }

                // log if it is an altdd
                final String urlString = descriptor.toExternalForm();
                if (!urlString.contains("META-INF/persistence.xml")) {
                    LOGGER.info("AltDD persistence.xml -> " + urlString);
                }

                persistenceUrls.add(descriptor);
                added.add(descriptor);
            }
        }

        // look for persistence-fragment.xml
        for (final URL url : urls) {
            // OPENEJB-1059: looking for an altdd persistence.xml file in all urls
            // delegates to xbean finder for going throughout the list
            final ResourceFinder finder = new ResourceFinder("", appModule.getClassLoader(), url);
            final Map<String, URL> descriptors = getDescriptors(finder, false);

            // if a persistence.xml has been found, just pull it to the list
            if (descriptors.containsKey("persistence-fragment.xml")) {
                final URL descriptor = descriptors.get("persistence-fragment.xml");

                if (persistenceFragmentsUrls.contains(descriptor)) {
                    continue;
                }

                // log if it is an altdd
                final String urlString = descriptor.toExternalForm();
                if (!urlString.contains("META-INF/persistence-fragment.xml")) {
                    LOGGER.info("AltDD persistence-fragment.xml -> " + urlString);
                }

                persistenceFragmentsUrls.add(descriptor);
                added.add(descriptor);
            }
        }

        return added;
    }

    public static Map<String, URL> getDescriptors(final URL moduleUrl) throws OpenEJBException {

        final ResourceFinder finder = new ResourceFinder(moduleUrl);
        return getDescriptors(finder);
    }

    private static Map<String, URL> getDescriptors(final ResourceFinder finder) throws OpenEJBException {
        return getDescriptors(finder, true);
    }

    private static Map<String, URL> getDescriptors(final ResourceFinder finder, final boolean log) throws OpenEJBException {
        try {

            return altDDSources(mapDescriptors(finder), log);

        } catch (final IOException e) {
            throw new OpenEJBException("Unable to determine descriptors in jar.", e);
        }
    }

    public static Map<String, URL> mapDescriptors(final ResourceFinder finder)
        throws IOException {
        final Map<String, URL> map = finder.getResourcesMap(META_INF);

        if (map.size() == 0) {

            for (final String descriptor : KNOWN_DESCRIPTORS) {

                final URL url = finder.getResource(META_INF + descriptor);
                if (url != null) {
                    map.put(descriptor, url);
                }
            }

        }
        return map;
    }

    /**
     * Modifies the map passed in with all the alt dd URLs found
     *
     * @param map Map
     * @param log boolean
     * @return the same map instance updated with alt dds
     */
    public static Map<String, URL> altDDSources(final Map<String, URL> map, final boolean log) {
        if (ALTDD == null || ALTDD.length() <= 0) {
            return map;
        }

        final List<String> list = new ArrayList<>(Arrays.asList(ALTDD.split(",")));
        Collections.reverse(list);

        final Map<String, URL> alts = new HashMap<>();

        for (String prefix : list) {
            prefix = prefix.trim();
            if (!prefix.matches(".*[.-]$")) {
                prefix += ".";
            }

            for (final Map.Entry<String, URL> entry : new HashMap<>(map).entrySet()) {
                String key = entry.getKey();
                final URL value = entry.getValue();
                if (key.startsWith(prefix)) {
                    key = key.substring(prefix.length());

                    alts.put(key, value);
                }
            }
        }

        for (final Map.Entry<String, URL> alt : alts.entrySet()) {
            final String key = alt.getKey();
            final URL value = alt.getValue();

            // don't add and log if the same key/value is already in the map
            if (value.equals(map.get(key))) {
                continue;
            }

            if (log) {
                LOGGER.info("AltDD " + key + " -> " + value.toExternalForm());
            }
            map.put(key, value);
        }

        return map;
    }

    public static Map<String, URL> getWebDescriptors(final File warFile) throws IOException {
        final Map<String, URL> descriptors = new TreeMap<>();

        // xbean resource finder has a bug when you use any uri but "META-INF"
        // and the jar file does not contain a directory entry for the uri

        if (warFile.isFile()) { // only to discover module type so xml file filtering is enough
            final URL jarURL = new URL("jar", "", -1, warFile.toURI().toURL() + "!/");
            try (JarFile jarFile = new JarFile(warFile)) {
                for (final JarEntry entry : Collections.list(jarFile.entries())) {
                    final String entryName = entry.getName();
                    if (!entry.isDirectory() && entryName.startsWith("WEB-INF/")
                        && (KNOWN_DESCRIPTORS.contains(entryName.substring("WEB-INF/".length())) || entryName.endsWith(".xml"))) { // + web.xml, web-fragment.xml...
                        descriptors.put(entryName, new URL(jarURL, entry.getName()));
                    }
                }
            } catch (final IOException e) {
                // most likely an invalid jar file
            }
        } else if (warFile.isDirectory()) {
            final File webInfDir = new File(warFile, "WEB-INF");
            if (webInfDir.isDirectory()) {
                final File[] files = webInfDir.listFiles();
                if (files != null) {
                    for (final File file : files) {
                        if (!file.isDirectory()) {
                            descriptors.put(file.getName(), file.toURI().toURL());
                        }
                    }
                }
            }

            // handle some few file(s) which can be in META-INF too
            final File webAppDdDir = new File(webInfDir, "classes/" + META_INF);
            if (webAppDdDir.isDirectory()) {
                final File[] files = webAppDdDir.listFiles();
                if (files != null) {
                    for (final File file : files) {
                        final String name = file.getName();
                        if (!descriptors.containsKey(name)) {
                            descriptors.put(name, file.toURI().toURL());
                        } else {
                            LOGGER.warning("Can't have a " + name + " in WEB-INF and WEB-INF/classes/META-INF, second will be ignored");
                        }
                    }
                }
            }
        }

        return descriptors;
    }

    protected File getFile(final URL warUrl) {
        if ("jar".equals(warUrl.getProtocol())) {
            String pathname = warUrl.getPath();

            // we only support file based jar urls
            if (!pathname.startsWith("file:")) {
                return null;
            }

            // strip off "file:"
            pathname = pathname.substring("file:".length());

            // file path has trailing !/ that must be stripped off
            pathname = pathname.substring(0, pathname.lastIndexOf('!'));

            try {
                pathname = URLDecoder.decode(pathname, "UTF-8");
            } catch (final Exception e) {
                //noinspection deprecation
                pathname = URLDecoder.decode(pathname);
            }
            return new File(pathname);
        } else if ("file".equals(warUrl.getProtocol())) {
            final String pathname = warUrl.getPath();
            try {
                return new File(URLDecoder.decode(pathname, "UTF-8"));
            } catch (final UnsupportedEncodingException e) {
                //noinspection deprecation
                return new File(URLDecoder.decode(pathname));
            }
        } else {
            return null;
        }
    }

    @SuppressWarnings({"unchecked"})
    public static Application unmarshal(final URL url) throws OpenEJBException {
        try {
            return ApplicationXml.unmarshal(url);
        } catch (final Exception e) {
            throw new OpenEJBException("Encountered error parsing the application.xml file: " + url.toExternalForm(), e);
        }
    }

    public static void scanDir(final File dir, final Map<String, URL> files, final String path) {
        scanDir(dir, files, path, true);
    }

    public static void scanDir(final File dir, final Map<String, URL> files, final String path, final boolean recursive) {
        final File[] dirFiles = dir.listFiles();
        if (dirFiles != null) {
            for (final File file : dirFiles) {
                if (file.isDirectory()) {
                    if (DeploymentsResolver.isExtractedDir(file)) {
                        continue;
                    }

                    if (recursive) {
                        scanDir(file, files, path + file.getName() + "/");
                    }
                } else {
                    final String name = file.getName();
                    try {
                        files.put(path + name, file.toURI().toURL());
                    } catch (final MalformedURLException e) {
                        LOGGER.warning("EAR path bad: " + path + name, e);
                    }
                }
            }
        }
    }

    public Class<? extends DeploymentModule> discoverModuleType(final URL baseUrl, final ClassLoader classLoader, final boolean searchForDescriptorlessApplications) throws IOException, UnknownModuleTypeException {
        final Set<RequireDescriptors> search = EnumSet.noneOf(RequireDescriptors.class);

        if (!searchForDescriptorlessApplications) {
            search.addAll(Arrays.asList(RequireDescriptors.values()));
        }

        return discoverModuleType(baseUrl, classLoader, search);
    }

    @SuppressWarnings("unchecked")
    public Class<? extends DeploymentModule> discoverModuleType(final URL baseUrl, final ClassLoader classLoader, final Set<RequireDescriptors> requireDescriptor) throws IOException, UnknownModuleTypeException {
        final boolean scanPotentialEjbModules = !requireDescriptor.contains(RequireDescriptors.EJB);
        final boolean scanPotentialClientModules = !requireDescriptor.contains(RequireDescriptors.CLIENT);


        URL pathToScanDescriptors = baseUrl;
        String path;
        if (baseUrl != null) {
            path = URLs.toFile(baseUrl).getAbsolutePath();
            if (baseUrl.getProtocol().equals("file") && path.endsWith("WEB-INF/classes/")) {
                //EJB found in WAR/WEB-INF/classes, scan WAR for ejb-jar.xml
                pathToScanDescriptors = new URL(path.substring(0, path.lastIndexOf("WEB-INF/classes/")));
            }
        } else {
            path = "";
        }

        final Map<String, URL> descriptors = getDescriptors(classLoader, pathToScanDescriptors);

        if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }

        if (path.endsWith(".xml") || path.endsWith(".json")) { // let say it is a resource module
            return ResourcesModule.class;
        }

        if (descriptors.containsKey("application.xml") || path.endsWith(".ear")) {
            return AppModule.class;
        }

        if (descriptors.containsKey("ra.xml") || path.endsWith(".rar")) {
            return ConnectorModule.class;
        }

        if (baseUrl != null) {
            final Map<String, URL> webDescriptors = getWebDescriptors(getFile(baseUrl));
            if (webDescriptors.containsKey("web.xml") || webDescriptors.containsKey(WEB_FRAGMENT_XML) // descriptor
                || path.endsWith(".war") || new File(path, "WEB-INF").exists()) { // webapp specific files
                return WebModule.class;
            }
        }

        if (descriptors.containsKey("ejb-jar.xml") || descriptors.containsKey("beans.xml")) {
            return EjbModule.class;
        }

        if (descriptors.containsKey("application-client.xml")) {
            return ClientModule.class;
        }

        final URL manifestUrl = descriptors.get("MANIFEST.MF");
        if (scanPotentialClientModules && manifestUrl != null) {
            // In this case scanPotentialClientModules really means "require application-client.xml"
            final InputStream is = new BufferedInputStream(manifestUrl.openStream());
            final Manifest manifest = new Manifest(is);
            final String mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
            if (mainClass != null) {
                return ClientModule.class;
            }
        }

        final Class<? extends DeploymentModule> cls = checkAnnotations(baseUrl, classLoader, scanPotentialEjbModules, scanPotentialClientModules);
        if (cls != null) {
            return cls;
        }

        if (descriptors.containsKey("persistence.xml") || descriptors.containsKey("persistence-fragment.xml")) {
            return PersistenceModule.class;
        }

        //#TOMEE-613
        final File file = URLs.toFile(baseUrl);
        if (DeploymentsResolver.isValidDirectory(file)) {

            final File[] files = file.listFiles();
            if (containsEarAssets(files)) {
                return AppModule.class;
            }
            if (containsWebAssets(files)) {
                return WebModule.class;
            }
        }

        final Class<? extends DeploymentModule> defaultType = (Class<? extends DeploymentModule>) SystemInstance.get().getOptions().get("openejb.default.deployment-module", (Class<?>) null);
        if (defaultType != null) {
            // should we do a better filtering? it seems enough for common cases.
            if (WebModule.class.equals(defaultType) && (path.endsWith(".jar!") || path.endsWith(".jar"))) {
                throw new UnknownModuleTypeException("Unknown module type: url=" + path + " which can't be a war.");
            }

            LOGGER.debug("type for '" + path + "' was not found, defaulting to " + defaultType.getSimpleName());
            return defaultType;
        }
        throw new UnknownModuleTypeException("Unknown module type: url=" + path); // baseUrl can be null
    }

    private static boolean containsWebAssets(final File[] files) {
        if (files != null) {
            for (final File file : files) {
                final String fn = file.getName().toLowerCase(Locale.ENGLISH);
                if (fn.endsWith(".jsp")) {
                    return true;
                }
                if (fn.endsWith(".html")) {
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean containsEarAssets(final File[] files) {
        if (files != null) {
            for (final File file : files) {
                final String fn = file.getName().toLowerCase(Locale.ENGLISH);
                if (fn.endsWith(".jar")) {
                    return true;
                }
                if (fn.endsWith(".war")) {
                    return true;
                }
                if (fn.endsWith(".rar")) {
                    return true;
                }
            }
        }
        return false;
    }


    private Map<String, URL> getDescriptors(final ClassLoader classLoader, final URL pathToScanDescriptors)
        throws IOException {
        final ResourceFinder finder = new ResourceFinder("", classLoader, pathToScanDescriptors);

        return altDDSources(mapDescriptors(finder), false);
    }

    private Class<? extends DeploymentModule> checkAnnotations(final URL urls, final ClassLoader classLoader, final boolean scanPotentialEjbModules, final boolean scanPotentialClientModules) {
        Class<? extends DeploymentModule> cls = null;
        if (scanPotentialEjbModules || scanPotentialClientModules) {
            final AnnotationFinder classFinder = new AnnotationFinder(classLoader, urls);

            final Set<Class<? extends DeploymentModule>> otherTypes = new LinkedHashSet<>();

            final AnnotationFinder.Filter filter = new AnnotationFinder.Filter() {
                final String packageName = LocalClient.class.getName().replace("LocalClient", "");

                @Override
                public boolean accept(final String annotationName) {
                    if (scanPotentialClientModules && annotationName.startsWith(packageName)) {
                        if (LocalClient.class.getName().equals(annotationName)) {
                            otherTypes.add(ClientModule.class);
                        }
                        if (RemoteClient.class.getName().equals(annotationName)) {
                            otherTypes.add(ClientModule.class);
                        }
                    } else if (scanPotentialEjbModules) {
                        if (annotationName.startsWith("javax.ejb.")) {
                            if ("javax.ejb.Stateful".equals(annotationName)) {
                                return true;
                            }
                            if ("javax.ejb.Stateless".equals(annotationName)) {
                                return true;
                            }
                            if ("javax.ejb.Singleton".equals(annotationName)) {
                                return true;
                            }
                            if ("javax.ejb.MessageDriven".equals(annotationName)) {
                                return true;
                            }
                        } else if (scanManagedBeans && "javax.annotation.ManagedBean".equals(annotationName)) {
                            return true;
                        }
                    }
                    return false;
                }
            };

            if (classFinder.find(filter)) {
                cls = EjbModule.class;
                // if it is a war just throw an error
                try {
                    if(LOGGER.isWarningEnabled()) {
                        final File ar = URLs.toFile(urls);
                        if (!ar.isDirectory() && !ar.getName().endsWith("ar")) { // guess no archive extension, check it is not a hidden war
                            try (JarFile war = new JarFile(ar)) {
                                final ZipEntry entry = war.getEntry("WEB-INF/");
                                if (entry != null) {
                                    LOGGER.warning("you deployed " + urls.toExternalForm() + ", it seems it is a war with no extension, please rename it");
                                }
                            }
                        }
                    }
                } catch (final Exception ignored) {
                    // no-op
                }
            }

            if (otherTypes.size() > 0) {
                // We may want some ordering/sorting if we add more type scanning
                cls = otherTypes.iterator().next();
            }
        }
        return cls;
    }

    public static File unpack(final File jarFile) throws OpenEJBException {
        if (jarFile.isDirectory() || jarFile.getName().endsWith(".jar")) {
            return jarFile;
        }

        String name = jarFile.getName();
        if (name.endsWith(".ear") || name.endsWith(".zip") || name.endsWith(".war") || name.endsWith(".rar")) {
            name = name.replaceFirst("....$", "");
        } else {
            name += ".unpacked";
        }

        try {
            return JarExtractor.extract(jarFile, name);
        } catch (final Throwable e) {
            throw new OpenEJBException("Unable to extract jar. " + e.getMessage(), e);
        }
    }

    protected static URL getFileUrl(final File jarFile) throws OpenEJBException {
        final URL baseUrl;
        try {
            baseUrl = jarFile.toURI().toURL();
        } catch (final MalformedURLException e) {
            throw new OpenEJBException("Malformed URL to app. " + e.getMessage(), e);
        }
        return baseUrl;
    }

    public static void reloadAltDD() {
        ALTDD = SystemInstance.get().getOptions().get(OPENEJB_ALTDD_PREFIX, (String) null);
    }

    public static class ExternalConfiguration {
        private final String[] classpath;
        private final Filter customerFilter;

        public ExternalConfiguration(final String[] classpath, final Filter customerFilter) {
            this.classpath = classpath;
            this.customerFilter = customerFilter;
        }

        public Filter getCustomerFilter() {
            return customerFilter;
        }

        public String[] getClasspath() {
            return classpath;
        }
    }
}
