EarUtil.java

package com.github.jonasrutishauser.cdi.maven.plugin.ear;

import static javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD;
import static javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA;
import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;

/*
 * Copyright (C) 2017 Jonas Rutishauser
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation version 3 of the License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.txt>.
 */

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.enterprise.inject.spi.Extension;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathException;
import javax.xml.xpath.XPathFactory;

import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.jboss.weld.bootstrap.api.CDI11Bootstrap;
import org.jboss.weld.bootstrap.api.TypeDiscoveryConfiguration;
import org.jboss.weld.bootstrap.spi.Deployment;
import org.jboss.weld.bootstrap.spi.EEModuleDescriptor;
import org.jboss.weld.bootstrap.spi.EEModuleDescriptor.ModuleType;
import org.jboss.weld.bootstrap.spi.Metadata;
import org.jboss.weld.bootstrap.spi.helpers.EEModuleDescriptorImpl;
import org.jboss.weld.environment.deployment.WeldBeanDeploymentArchive;
import org.jboss.weld.environment.deployment.discovery.DiscoveryStrategy;
import org.jboss.weld.environment.deployment.discovery.DiscoveryStrategyFactory;
import org.jboss.weld.resources.ClassLoaderResourceLoader;
import org.jboss.weld.resources.spi.ResourceLoader;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.github.jonasrutishauser.cdi.maven.plugin.ArchiveUtil;
import com.github.jonasrutishauser.cdi.maven.plugin.war.WarUtil;
import com.github.jonasrutishauser.cdi.maven.plugin.weld.EarDeployment;
import com.github.jonasrutishauser.cdi.maven.plugin.weld.JarsBeanArchiveScanner;

public class EarUtil extends ArchiveUtil {

    private final ArchiverManager archiverManager;
    private final EjbUtil ejbUtil = new EjbUtil();

    private final Set<File> ejbs = new HashSet<>();
    private final Set<File> libraries = new HashSet<>();
    private final Set<WarUtil> wars = new HashSet<>();

    private ClassLoader earClassloader;

    public EarUtil(ArchiverManager archiverManager) {
        this.archiverManager = archiverManager;
    }

    public Deployment createDeployment(CDI11Bootstrap bootstrap) throws MojoExecutionException {
        ResourceLoader resourceLoader = new ClassLoaderResourceLoader(earClassloader);
        Set<Metadata<Extension>> extensions = getExtensions(bootstrap);
        addDefaultExtensions(extensions);
        TypeDiscoveryConfiguration typeDiscoveryConfiguration = bootstrap.startExtensions(extensions);
        return new EarDeployment(resourceLoader, bootstrap, getBeanDeploymentArchives(resourceLoader, bootstrap,
                typeDiscoveryConfiguration.getKnownBeanDefiningAnnotations()), extensions);
    }

    @Override
    public ClassLoader getClassLoader() {
        return earClassloader;
    }

    private Set<WeldBeanDeploymentArchive> getBeanDeploymentArchives(ResourceLoader resourceLoader,
            CDI11Bootstrap bootstrap, Set<Class<? extends Annotation>> beanDefiningAnnotations) {
        return Stream
                .of(getEjbArchives(resourceLoader, bootstrap, beanDefiningAnnotations),
                        getLibraryArchives(resourceLoader, bootstrap, beanDefiningAnnotations),
                        getWarArchives(bootstrap, beanDefiningAnnotations))
                .flatMap(Set::stream).collect(Collectors.toCollection(HashSet::new));
    }

    private Set<WeldBeanDeploymentArchive> getWarArchives(CDI11Bootstrap bootstrap,
            Set<Class<? extends Annotation>> beanDefiningAnnotations) {
        return wars.stream().map(util -> util.createArchives(bootstrap, beanDefiningAnnotations)).flatMap(Set::stream)
                .collect(Collectors.toSet());
    }

    private Set<WeldBeanDeploymentArchive> getLibraryArchives(ResourceLoader resourceLoader, CDI11Bootstrap bootstrap,
            Set<Class<? extends Annotation>> beanDefiningAnnotations) {
        DiscoveryStrategy strategy = DiscoveryStrategyFactory.create(resourceLoader, bootstrap, beanDefiningAnnotations,
                false);
        strategy.setScanner(new JarsBeanArchiveScanner(bootstrap, libraries));
        Set<WeldBeanDeploymentArchive> archives = strategy.performDiscovery();
        archives.forEach(archive -> archive.getServices().add(EEModuleDescriptor.class,
                new EEModuleDescriptorImpl(archive.getId(), ModuleType.EAR)));
        return archives;
    }

    private Set<WeldBeanDeploymentArchive> getEjbArchives(ResourceLoader resourceLoader, CDI11Bootstrap bootstrap,
            Set<Class<? extends Annotation>> beanDefiningAnnotations) {
        Set<Class<? extends Annotation>> annotations = new HashSet<>(beanDefiningAnnotations);
        ejbUtil.addBeanDefiningAnnotations(annotations);
        DiscoveryStrategy strategy = DiscoveryStrategyFactory.create(resourceLoader, bootstrap, annotations, false);
        strategy.setScanner(new JarsBeanArchiveScanner(bootstrap, ejbs));
        return strategy.performDiscovery().stream().map(ejbUtil::addEjbDescriptors).collect(Collectors.toSet());
    }

    private Set<Metadata<Extension>> getExtensions(CDI11Bootstrap bootstrap) {
        return Stream
                .of(Collections.singleton(earClassloader),
                        wars.stream().map(WarUtil::getClassLoader).collect(Collectors.toSet()))
                .flatMap(Collection::stream)
                .flatMap(
                        classloader -> StreamSupport.stream(bootstrap.loadExtensions(classloader).spliterator(), false))
                .collect(Collectors.toSet());
    }

    @Override
    public void init(File workDirectory, File earFile, ClassLoader parentClassloader) throws MalformedURLException {
        UnArchiver unArchiver;
        try {
            unArchiver = archiverManager.getUnArchiver(earFile);
        } catch (NoSuchArchiverException e) {
            throw new IllegalStateException(e);
        }
        File extractedDir = new File(workDirectory, earFile.getName());
        extractedDir.mkdirs();
        unArchiver.setDestDirectory(extractedDir);
        unArchiver.setSourceFile(earFile);
        unArchiver.extract();
        File libDir = new File(extractedDir, "lib");
        Set<File> warFiles = new HashSet<>();
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setAttribute(ACCESS_EXTERNAL_DTD, "");
            factory.setAttribute(ACCESS_EXTERNAL_SCHEMA, "");
            factory.setFeature(FEATURE_SECURE_PROCESSING, true);
            DocumentBuilder documentBuilder = factory.newDocumentBuilder();
            Document doc = documentBuilder.parse(new File(extractedDir, "META-INF/application.xml"));
            XPath xPath = XPathFactory.newInstance().newXPath();
            NodeList modules = (NodeList) xPath.evaluate("//module/*", doc, XPathConstants.NODESET);
            for (int i = 0; i < modules.getLength(); i++) {
                Element module = (Element) modules.item(i);
                if ("ejb".equals(module.getNodeName())) {
                    ejbs.add(new File(extractedDir, module.getTextContent()));
                } else if ("web".equals(module.getNodeName())) {
                    File warFile = new File(extractedDir,
                            (String) xPath.evaluate("web-uri/text()", module, XPathConstants.STRING));
                    warFiles.add(warFile);
                } else {
                    libraries.add(new File(extractedDir, module.getTextContent()));
                }
            }
            Element libraryDirectory = (Element) xPath.evaluate("//library-directory", doc, XPathConstants.NODE);
            if (libraryDirectory != null) {
                libDir = new File(extractedDir, libraryDirectory.getTextContent());
            }
        } catch (ParserConfigurationException | SAXException | XPathException | IOException e) {
            LoggerFactory.getLogger(getClass()).warn("failed to read deployment descriptor of ear", e);
        }
        File[] jars = libDir.listFiles(file -> file.getName().endsWith(".jar"));
        if (jars != null) {
            libraries.addAll(Arrays.asList(jars));
        }
        List<URL> urls = new ArrayList<>(ejbs.size() + libraries.size());
        for (File ejb : ejbs) {
            urls.add(ejb.toURI().toURL());
        }
        for (File library : libraries) {
            urls.add(library.toURI().toURL());
        }
        earClassloader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parentClassloader);
        for (File warFile : warFiles) {
            wars.add(WarUtil.create(archiverManager, workDirectory, warFile, earClassloader));
        }
    }

}