001/**
002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.runtime.manager.service;
017
018import static java.util.Collections.emptyMap;
019import static java.util.stream.Collectors.toList;
020
021import java.io.ObjectStreamException;
022import java.io.Serializable;
023import java.lang.reflect.Field;
024import java.lang.reflect.Modifier;
025import java.lang.reflect.ParameterizedType;
026import java.lang.reflect.Type;
027import java.util.Collection;
028import java.util.Map;
029import java.util.function.Supplier;
030import java.util.stream.Stream;
031
032import org.talend.sdk.component.api.service.Service;
033import org.talend.sdk.component.api.service.configuration.Configuration;
034import org.talend.sdk.component.api.service.injector.Injector;
035import org.talend.sdk.component.runtime.manager.asm.ProxyGenerator;
036import org.talend.sdk.component.runtime.manager.reflect.ReflectionService;
037import org.talend.sdk.component.runtime.serialization.SerializableService;
038
039import lombok.AllArgsConstructor;
040
041// internal service for now, we can refactor it later to expose it if needed
042@AllArgsConstructor
043public class InjectorImpl implements Serializable, Injector {
044
045    private final String plugin;
046
047    private final ReflectionService reflectionService;
048
049    private final ProxyGenerator proxyGenerator;
050
051    private final Map<Class<?>, Object> services;
052
053    @Override
054    public <T> T inject(final T instance) {
055        if (instance == null) {
056            return null;
057        }
058        doInject(instance.getClass(), unwrap(instance));
059        return instance;
060    }
061
062    private Object unwrap(final Object instance) {
063        if (instance.getClass().getName().endsWith("$$TalendServiceProxy")) {
064            try {
065                return proxyGenerator.getHandler(instance).getDelegate();
066            } catch (final IllegalStateException nsfe) {
067                // no-op
068            }
069        }
070        return instance;
071    }
072
073    private <T> void doInject(final Class<?> type, final T instance) {
074        if (type == Object.class || type == null) {
075            return;
076        }
077        final Field[] fields = type.getDeclaredFields();
078        Stream
079                .of(fields)
080                .filter(field -> !Modifier.isStatic(field.getModifiers()))
081                .filter(field -> field.isAnnotationPresent(Service.class))
082                .peek(f -> {
083                    if (!f.isAccessible()) {
084                        f.setAccessible(true);
085                    }
086                })
087                .forEach(field -> {
088                    Object value = services.get(field.getType());
089                    if (value == null && ParameterizedType.class.isInstance(field.getGenericType())) {
090                        final ParameterizedType pt = ParameterizedType.class.cast(field.getGenericType());
091                        if (Class.class.isInstance(pt.getRawType())
092                                && Collection.class.isAssignableFrom(Class.class.cast(pt.getRawType()))) {
093                            final Type serviceType = pt.getActualTypeArguments()[0];
094                            if (Class.class.isInstance(serviceType)) {
095                                final Class<?> serviceClass = Class.class.cast(serviceType);
096                                value = services
097                                        .entrySet()
098                                        .stream()
099                                        .filter(e -> serviceClass.isAssignableFrom(e.getKey()))
100                                        .collect(toList());
101                            }
102                        }
103                    }
104                    if (value != null) {
105                        try {
106                            field.set(instance, value);
107                        } catch (final IllegalAccessException e) {
108                            throw new IllegalArgumentException(e);
109                        }
110                    }
111                });
112        Stream
113                .of(fields)
114                .filter(field -> !Modifier.isStatic(field.getModifiers()))
115                .filter(field -> field.isAnnotationPresent(Configuration.class))
116                .peek(field -> {
117                    if (Supplier.class != field.getType()
118                            || !ParameterizedType.class.isInstance(field.getGenericType())) {
119                        throw new IllegalArgumentException("Field " + field + " is not a Supplier<X>,\n"
120                                + "it will not be injected otherwise it wouldn't be up to date,\n"
121                                + "did you mean Supplier<" + field.getType() + "> ?");
122                    }
123                })
124                .peek(f -> {
125                    if (!f.isAccessible()) {
126                        f.setAccessible(true);
127                    }
128                })
129                .forEach(field -> {
130                    try {
131                        final Class<?> configClass = Class.class
132                                .cast(ParameterizedType.class.cast(field.getGenericType()).getActualTypeArguments()[0]);
133                        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
134                        final Supplier<?> supplier = () -> {
135                            try {
136                                return reflectionService
137                                        .createConfigFactory(services, loader,
138                                                reflectionService.createContextualSupplier(loader), field.getName(),
139                                                field.getAnnotation(Configuration.class), field.getAnnotations(),
140                                                configClass)
141                                        .apply(emptyMap());
142                            } catch (final NoSuchMethodException e) {
143                                throw new IllegalStateException(e);
144                            }
145                        };
146                        field.set(instance, supplier);
147                    } catch (final IllegalAccessException e) {
148                        throw new IllegalArgumentException(e);
149                    }
150                });
151        if (type.getSuperclass() != type) {
152            doInject(type.getSuperclass(), instance);
153        }
154    }
155
156    Object writeReplace() throws ObjectStreamException {
157        return new SerializableService(plugin, Injector.class.getName());
158    }
159}