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

io.helidon.integrations.graal.mp.nativeimage.extension.WeldFeature Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2019, 2023 Oracle and/or its affiliates.
 *
 * Licensed 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 io.helidon.integrations.graal.mp.nativeimage.extension;

import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import io.helidon.integrations.graal.nativeimage.extension.NativeConfig;

import jakarta.enterprise.inject.spi.Bean;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonReaderFactory;
import jakarta.json.stream.JsonParsingException;
import org.graalvm.nativeimage.hosted.Feature.DuringSetupAccess;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.jboss.weld.bean.proxy.ClientProxyFactory;
import org.jboss.weld.bean.proxy.ClientProxyProvider;
import org.jboss.weld.manager.BeanManagerImpl;
import org.jboss.weld.util.Proxies;

/**
 * An automatic feature for native-image to
 *  register Weld specific stuff.
 */
class WeldFeature {
    private static final boolean TRACE = NativeConfig.option("weld.trace", false);
    private static final boolean WARN = NativeConfig.option("weld.warn", false);

    void duringSetup(DuringSetupAccess access) {
        Class beanManagerClass = access.findClassByName("org.jboss.weld.manager.BeanManagerImpl");
        Set processed = new HashSet<>();
        Set> processedExplicitProxy = new HashSet<>();
        Set processedBeanManagers = Collections.newSetFromMap(new IdentityHashMap<>());
        List weldProxyConfigs = weldProxyConfigurations(access);

        trace(() -> "Weld feature");

        access.registerObjectReplacer((obj) -> {
            if (beanManagerClass.isInstance(obj) && processedBeanManagers.add(obj)) {
                try {
                    BeanManagerImpl bm = (BeanManagerImpl) obj;
                    ClientProxyProvider cpp = bm.getClientProxyProvider();

                    String contextId = bm.getContextId();
                    List> beans = bm.getBeans();

                    iterateBeans(bm, cpp, processed, beans);

                    weldProxyConfigs.forEach(proxy -> {
                        initializeProxy(access,
                                        processedExplicitProxy,
                                        contextId,
                                        // bean class is the class defining the beans (such as bean producer, or the bean type
                                        // itself if this is a managed bean - used to generate name of the client proxy class
                                        proxy.beanClass,
                                        // actual types of the bean - used to generate the client proxy class
                                        proxy.types);
                    });
                } catch (Exception ex) {
                    warn(() -> "Error processing object " + obj);
                    warn(() -> "  " + ex.getClass().getName() + ": " + ex.getMessage());
                }
            }
            return obj;
        });
    }

    private void trace(Supplier message) {
        if (TRACE) {
            System.out.println(message.get());
        }
    }

    private void warn(Supplier message) {
        if (WARN) {
            System.err.println(message.get());
        }
    }

    private void initializeProxy(DuringSetupAccess access,
                                 Set> processedExplicitProxy,
                                 String contextId,
                                 String beanClassName,
                                 String... typeClasses) {

        trace(() -> beanClassName);

        Class beanClass = access.findClassByName(beanClassName);
        if (null == beanClass) {
            warn(() -> "  Bean class not found: " + beanClassName);
            return;
        }

        Set types = new HashSet<>();

        for (String typeClass : typeClasses) {
            Class theClass = access.findClassByName(typeClass);
            if (null == theClass) {
                warn(() -> "  Type class not found: " + typeClass);
                return;
            }
            types.add(theClass);
        }

        if (processedExplicitProxy.add(types)) {
            Bean theBean = new ProxyBean(beanClass, types);

            Proxies.TypeInfo typeInfo = Proxies.TypeInfo.of(types);

            ClientProxyFactory cpf = new ClientProxyFactory<>(contextId, typeInfo.getSuperClass(), types, theBean);

            Class proxyClass = cpf.getProxyClass();

            trace(() -> "  Registering proxy class " + proxyClass.getName() + " with types " + types);

            RuntimeReflection.register(proxyClass);
            RuntimeReflection.register(proxyClass.getConstructors());
            RuntimeReflection.register(proxyClass.getDeclaredConstructors());
            RuntimeReflection.register(proxyClass.getMethods());
            RuntimeReflection.register(proxyClass.getDeclaredMethods());
            RuntimeReflection.register(proxyClass.getFields());
            RuntimeReflection.register(proxyClass.getDeclaredFields());
        }
    }

    private void iterateBeans(BeanManagerImpl bm,
                              ClientProxyProvider cpp,
                              Set processed,
                              Collection> beans) {
        for (Bean bean : beans) {
            Set beanTypes = bean.getTypes();

            BeanId id = new BeanId(bean.getBeanClass(), beanTypes);

            // the id is a combination of bean class and bean types, we missed types before (when using bean class only)
            if (!processed.add(id)) {
                continue;
            }

            try {
                Object proxy = cpp.getClientProxy(bean);
                trace(() -> "Created proxy for bean class: "
                        + bean.getBeanClass().getName()
                        + ", bean type: "
                        + beanTypes
                        + ", proxy class: "
                        + proxy.getClass().getName());
            } catch (Throwable e) {
                // try interfaces
                warn(() -> "Failed to create a proxy for bean "
                        + bean.getBeanClass() + ", "
                        + e.getClass().getName() + ": "
                        + e.getMessage() + " - this bean will not work in native-image");
            }

            // now we also need to handle all types
            beanTypes.forEach(type -> iterateBeans(bm, cpp, processed, bm.getBeans(type)));
        }
    }

    static List weldProxyConfigurations(DuringSetupAccess access) {
        try {
            ClassLoader classLoader = access.getApplicationClassLoader();
            Enumeration resources = classLoader
                    .getResources("META-INF/helidon/native-image/weld-proxies.json");

            JsonReaderFactory readerFactory = Json.createReaderFactory(Map.of());
            List weldProxies = new ArrayList<>();
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                JsonArray proxies;
                try {
                    proxies = readerFactory.createReader(url.openStream()).readArray();
                } catch (JsonParsingException e) {
                    throw new NativeImageException("Failed to read JSON config: " + url, e);
                }
                proxies.forEach(jsonValue -> {
                    weldProxies.add(new WeldProxyConfig((JsonObject) jsonValue));
                });
            }
            return weldProxies;
        } catch (IOException e) {
            throw new IllegalStateException("Failed to get resources", e);
        }
    }

    private static class WeldProxyConfig {
        // bean class
        private final String beanClass;
        // bean types
        private final String[] types;

        private WeldProxyConfig(JsonObject jsonValue) {
            this.beanClass = jsonValue.getString("bean-class");
            JsonArray array = jsonValue.getJsonArray("ifaces");
            int size = array.size();
            types = new String[size];
            for (int i = 0; i < size; i++) {
                types[i] = array.getString(i);
            }
        }
    }

    private static final class BeanId {
        private final Class beanClass;
        private final Set types;

        private BeanId(Class beanClass, Set types) {
            this.beanClass = beanClass;
            this.types = types;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            BeanId beanId = (BeanId) o;
            return beanClass.equals(beanId.beanClass)
                    && types.equals(beanId.types);
        }

        @Override
        public int hashCode() {
            return Objects.hash(beanClass, types);
        }

        @Override
        public String toString() {
            return beanClass.getName() + ": " + types;
        }
    }
}