All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.arc.arquillian.Deployer Maven / Gradle / Ivy

The newest version!
package io.quarkus.arc.arquillian;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Indexer;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.Filters;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.ArchiveAsset;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;

import io.quarkus.arc.arquillian.utils.Archives;
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BeanArchives;
import io.quarkus.arc.processor.BeanDefiningAnnotation;
import io.quarkus.arc.processor.BeanProcessor;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.bcextensions.ExtensionsEntryPoint;

final class Deployer {
    private final Archive deploymentArchive;
    private final DeploymentDir deploymentDir;
    private final String testClass;

    private final List beanArchives = new ArrayList<>();

    Deployer(Archive deploymentArchive, DeploymentDir deploymentDir, String testClass) {
        this.deploymentArchive = deploymentArchive;
        this.deploymentDir = deploymentDir;
        this.testClass = testClass;
    }

    DeploymentClassLoader deploy() throws DeploymentException {
        try {
            if (deploymentArchive instanceof JavaArchive) {
                explodeJar();
            } else if (deploymentArchive instanceof WebArchive) {
                explodeWar();
            } else {
                throw new DeploymentException("Unknown archive type: " + deploymentArchive);
            }

            generate();

            return new DeploymentClassLoader(deploymentDir);
        } catch (IOException | ExecutionException | InterruptedException e) {
            throw new DeploymentException("Deployment failed", e);
        }
    }

    private void explodeJar() throws IOException {
        Archives.explode(deploymentArchive, "/", deploymentDir.appClasses);

        BeanArchive beanArchive = BeanArchive.detect(deploymentArchive);
        if (beanArchive != null) {
            beanArchives.add(beanArchive);
        }
    }

    private void explodeWar() throws IOException {
        Archives.explode(deploymentArchive, "/WEB-INF/classes/", deploymentDir.appClasses);

        BeanArchive beanArchive = BeanArchive.detect(deploymentArchive);
        if (beanArchive != null) {
            beanArchives.add(beanArchive);
        }

        Map libs = deploymentArchive.getContent(Filters.include("^/WEB-INF/lib/.*\\.jar$"));
        for (Map.Entry entry : libs.entrySet()) {
            String path = entry.getKey().get();
            Asset asset = entry.getValue().getAsset();

            String jarFile = path.replace("/WEB-INF/lib/", "");
            Path jarFilePath = deploymentDir.appLibraries.resolve(jarFile);
            Archives.copy(asset, jarFilePath);

            if (asset instanceof ArchiveAsset) {
                BeanArchive nestedBeanArchive = BeanArchive.detect(((ArchiveAsset) asset).getArchive());
                if (nestedBeanArchive != null) {
                    beanArchives.add(nestedBeanArchive);
                }
            }
        }
    }

    private void generate() throws IOException, ExecutionException, InterruptedException {
        Index applicationIndex = buildApplicationIndex();

        try (Closeable ignored = withDeploymentClassLoader()) {
            ExtensionsEntryPoint buildCompatibleExtensions = new ExtensionsEntryPoint();
            Set additionalClasses = new HashSet<>();
            buildCompatibleExtensions.runDiscovery(applicationIndex, additionalClasses);

            IndexView beanArchiveIndex = buildImmutableBeanArchiveIndex(applicationIndex, additionalClasses);

            BeanProcessor beanProcessor = BeanProcessor.builder()
                    .setName(deploymentDir.root.getFileName().toString())
                    .setImmutableBeanArchiveIndex(beanArchiveIndex)
                    .setComputingBeanArchiveIndex(BeanArchives.buildComputingBeanArchiveIndex(
                            Thread.currentThread().getContextClassLoader(), new ConcurrentHashMap<>(),
                            beanArchiveIndex))
                    .setApplicationIndex(applicationIndex)
                    .setStrictCompatibility(true)
                    .setTransformUnproxyableClasses(false)
                    .setRemoveUnusedBeans(false)
                    .setBuildCompatibleExtensions(buildCompatibleExtensions)
                    .setAdditionalBeanDefiningAnnotations(Set.of(
                            new BeanDefiningAnnotation(DotName.createSimple(ExtraBean.class))))
                    .addAnnotationTransformation(new AnnotationsTransformer() {
                        @Override
                        public boolean appliesTo(AnnotationTarget.Kind kind) {
                            return kind == AnnotationTarget.Kind.CLASS;
                        }

                        @Override
                        public void transform(TransformationContext ctx) {
                            if (testClass.equals(ctx.getTarget().asClass().name().toString())) {
                                // make the test class a bean
                                ctx.transform().add(ExtraBean.class).done();
                            }
                            if (additionalClasses.contains(ctx.getTarget().asClass().name().toString())) {
                                // make all the `@Discovery`-registered classes beans
                                ctx.transform().add(ExtraBean.class).done();
                            }
                        }
                    })
                    .setOutput(resource -> {
                        switch (resource.getType()) {
                            case JAVA_CLASS:
                                resource.writeTo(deploymentDir.generatedClasses.toFile());
                                break;
                            case SERVICE_PROVIDER:
                                resource.writeTo(deploymentDir.generatedServices.toFile());
                                break;
                            default:
                                throw new IllegalArgumentException("Unknown resource type " + resource.getType());
                        }
                    })
                    .build();
            beanProcessor.process();
        }
    }

    private Index buildApplicationIndex() throws IOException {
        Indexer indexer = new Indexer();
        try (Stream appClasses = Files.walk(deploymentDir.appClasses)) {
            List classFiles = appClasses.filter(it -> it.toString().endsWith(".class")).collect(Collectors.toList());
            for (Path classFile : classFiles) {
                try (InputStream in = Files.newInputStream(classFile)) {
                    indexer.index(in);
                }
            }
        }
        try (Stream appLibraries = Files.walk(deploymentDir.appLibraries)) {
            List jarFiles = appLibraries.filter(it -> it.toString().endsWith(".jar")).collect(Collectors.toList());
            for (Path jarFile : jarFiles) {
                try (JarFile jar = new JarFile(jarFile.toFile())) {
                    Enumeration entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        if (entry.getName().endsWith(".class")) {
                            try (InputStream in = jar.getInputStream(entry)) {
                                indexer.index(in);
                            }
                        }
                    }
                }
            }
        }
        return indexer.complete();
    }

    private IndexView buildImmutableBeanArchiveIndex(Index applicationIndex, Set additionalClasses) throws IOException {
        Indexer indexer = new Indexer();

        Set seen = new HashSet<>();

        // 1. classes in bean archives
        for (BeanArchive beanArchive : beanArchives) {
            for (String classFile : beanArchive.classes) {
                indexFromTCCL(indexer, classFile);
                seen.add(classFile);
            }
        }

        // 2. additional classes added through build compatible extensions
        for (String additionalClass : additionalClasses) {
            String classFile = additionalClass.replace('.', '/') + ".class";
            if (seen.contains(classFile)) {
                continue;
            }
            indexFromTCCL(indexer, classFile);
            seen.add(classFile);
        }

        // 3. test class
        {
            String classFile = testClass.replace('.', '/') + ".class";
            if (!seen.contains(classFile)) {
                indexFromTCCL(indexer, classFile);
                seen.add(classFile);
            }
        }

        // 4. CDI-related annotations (scopes, qualifiers, interceptor bindings, stereotypes)
        // CDI recognizes them even if they come from an archive that is not a bean archive
        Set metaAnnotations = Set.of(DotNames.SCOPE, DotNames.NORMAL_SCOPE, DotNames.QUALIFIER,
                DotNames.INTERCEPTOR_BINDING, DotNames.STEREOTYPE);
        for (DotName metaAnnotation : metaAnnotations) {
            for (AnnotationInstance annotation : applicationIndex.getAnnotations(metaAnnotation)) {
                if (annotation.target().kind().equals(AnnotationTarget.Kind.CLASS)) {
                    String annotationClass = annotation.target().asClass().name().toString();
                    String classFile = annotationClass.replace('.', '/') + ".class";
                    if (seen.contains(classFile)) {
                        continue;
                    }
                    indexFromTCCL(indexer, classFile);
                    seen.add(classFile);
                }
            }
        }

        return BeanArchives.buildImmutableBeanArchiveIndex(indexer.complete());
    }

    private void indexFromTCCL(Indexer indexer, String classFile) throws IOException {
        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
        try (InputStream in = tccl.getResourceAsStream(classFile)) {
            indexer.index(in);
        }
    }

    private Closeable withDeploymentClassLoader() throws IOException {
        ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
        DeploymentClassLoader newCl = new DeploymentClassLoader(deploymentDir);

        Thread.currentThread().setContextClassLoader(newCl);
        return new Closeable() {
            @Override
            public void close() throws IOException {
                Thread.currentThread().setContextClassLoader(oldCl);
                newCl.close();
            }
        };
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy