001/**
002 * Copyright (C) 2006-2025 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.Comparator.comparing;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.lang.reflect.Field;
024import java.nio.file.Path;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Iterator;
028import java.util.List;
029import java.util.Map;
030import java.util.Properties;
031import java.util.ServiceLoader;
032import java.util.concurrent.Executors;
033import java.util.concurrent.ScheduledExecutorService;
034import java.util.concurrent.atomic.AtomicReference;
035import java.util.function.Function;
036import java.util.function.Supplier;
037import java.util.stream.Stream;
038
039import javax.json.JsonBuilderFactory;
040import javax.json.JsonReaderFactory;
041import javax.json.JsonWriterFactory;
042import javax.json.bind.Jsonb;
043import javax.json.bind.JsonbBuilder;
044import javax.json.bind.JsonbConfig;
045import javax.json.bind.spi.JsonbProvider;
046import javax.json.spi.JsonProvider;
047import javax.json.stream.JsonGeneratorFactory;
048import javax.json.stream.JsonParserFactory;
049
050import org.apache.johnzon.mapper.MapperBuilder;
051import org.talend.sdk.component.api.record.RecordPointerFactory;
052import org.talend.sdk.component.api.service.cache.LocalCache;
053import org.talend.sdk.component.api.service.configuration.LocalConfiguration;
054import org.talend.sdk.component.api.service.dependency.Resolver;
055import org.talend.sdk.component.api.service.factory.ObjectFactory;
056import org.talend.sdk.component.api.service.http.HttpClientFactory;
057import org.talend.sdk.component.api.service.injector.Injector;
058import org.talend.sdk.component.api.service.record.RecordBuilderFactory;
059import org.talend.sdk.component.api.service.record.RecordService;
060import org.talend.sdk.component.api.service.source.ProducerFinder;
061import org.talend.sdk.component.runtime.manager.asm.ProxyGenerator;
062import org.talend.sdk.component.runtime.manager.json.PreComputedJsonpProvider;
063import org.talend.sdk.component.runtime.manager.proxy.JavaProxyEnricherFactory;
064import org.talend.sdk.component.runtime.manager.reflect.ReflectionService;
065import org.talend.sdk.component.runtime.manager.service.api.ComponentInstantiator;
066import org.talend.sdk.component.runtime.manager.service.configuration.PropertiesConfiguration;
067import org.talend.sdk.component.runtime.manager.service.http.HttpClientFactoryImpl;
068import org.talend.sdk.component.runtime.manager.util.Lazy;
069import org.talend.sdk.component.runtime.manager.util.MemoizingSupplier;
070import org.talend.sdk.component.runtime.manager.xbean.registry.EnrichedPropertyEditorRegistry;
071import org.talend.sdk.component.runtime.record.json.RecordJsonGenerator;
072
073import lombok.RequiredArgsConstructor;
074import lombok.extern.slf4j.Slf4j;
075
076@Slf4j
077@RequiredArgsConstructor
078public class DefaultServiceProvider {
079
080    private final ReflectionService reflections;
081
082    private final JsonProvider jsonpProvider;
083
084    private final JsonGeneratorFactory jsonpGeneratorFactory;
085
086    private final JsonReaderFactory jsonpReaderFactory;
087
088    private final JsonBuilderFactory jsonpBuilderFactory;
089
090    private final JsonParserFactory jsonpParserFactory;
091
092    private final JsonWriterFactory jsonpWriterFactory;
093
094    private final JsonbConfig jsonbConfig;
095
096    private final JsonbProvider jsonbProvider;
097
098    private final ProxyGenerator proxyGenerator;
099
100    private final JavaProxyEnricherFactory javaProxyEnricherFactory;
101
102    private final Collection<LocalConfiguration> localConfigurations;
103
104    private final Function<String, RecordBuilderFactory> recordBuilderFactoryProvider;
105
106    private final EnrichedPropertyEditorRegistry propertyEditorRegistry;
107
108    private final Supplier<ScheduledExecutorService> executorService =
109            new MemoizingSupplier<>(this::buildExecutorService);
110
111    public <T> T lookup(final String id, final ClassLoader loader, final Supplier<List<InputStream>> localConfigLookup,
112            final Function<String, Path> resolver, final Class<T> api,
113            final AtomicReference<Map<Class<?>, Object>> services, final ComponentInstantiator.Builder instantiators) {
114        return api.cast(doLookup(id, loader, localConfigLookup, resolver, api, services, instantiators));
115    }
116
117    private Object doLookup(final String id, final ClassLoader loader,
118            final Supplier<List<InputStream>> localConfigLookup, final Function<String, Path> resolver,
119            final Class<?> api, final AtomicReference<Map<Class<?>, Object>> services,
120            final ComponentInstantiator.Builder instantiators) {
121        if (JsonProvider.class == api) {
122            return new PreComputedJsonpProvider(id, jsonpProvider, jsonpParserFactory, jsonpWriterFactory,
123                    jsonpBuilderFactory, jsonpGeneratorFactory, jsonpReaderFactory);
124        }
125        if (JsonBuilderFactory.class == api) {
126            return javaProxyEnricherFactory
127                    .asSerializable(loader, id, JsonBuilderFactory.class.getName(), jsonpBuilderFactory);
128        }
129        if (JsonParserFactory.class == api) {
130            return javaProxyEnricherFactory
131                    .asSerializable(loader, id, JsonParserFactory.class.getName(), jsonpParserFactory);
132        }
133        if (JsonReaderFactory.class == api) {
134            return javaProxyEnricherFactory
135                    .asSerializable(loader, id, JsonReaderFactory.class.getName(), jsonpReaderFactory);
136        }
137        if (JsonWriterFactory.class == api) {
138            return javaProxyEnricherFactory
139                    .asSerializable(loader, id, JsonWriterFactory.class.getName(), jsonpWriterFactory);
140        }
141        if (JsonGeneratorFactory.class == api) {
142            return javaProxyEnricherFactory
143                    .asSerializable(loader, id, JsonGeneratorFactory.class.getName(), jsonpGeneratorFactory);
144        }
145        if (Jsonb.class == api) {
146            final JsonbBuilder jsonbBuilder = createPojoJsonbBuilder(id,
147                    Lazy
148                            .lazy(() -> Jsonb.class
149                                    .cast(doLookup(id, loader, localConfigLookup, resolver, Jsonb.class, services,
150                                            instantiators))));
151            return new GenericOrPojoJsonb(id, jsonbProvider
152                    .create()
153                    .withProvider(jsonpProvider) // reuses the same memory buffering
154                    .withConfig(jsonbConfig)
155                    .build(), jsonbBuilder.build());
156        }
157        if (LocalConfiguration.class == api) {
158            final List<LocalConfiguration> containerConfigurations = new ArrayList<>(localConfigurations);
159            if (!Boolean.getBoolean("talend.component.configuration." + id + ".ignoreLocalConfiguration")) {
160                final Stream<InputStream> localConfigs = localConfigLookup.get().stream();
161                final Properties aggregatedLocalConfigs = aggregateConfigurations(localConfigs);
162                if (!aggregatedLocalConfigs.isEmpty()) {
163                    containerConfigurations.add(new PropertiesConfiguration(aggregatedLocalConfigs));
164                }
165            }
166            return new LocalConfigurationService(containerConfigurations, id);
167        }
168        if (RecordBuilderFactory.class == api) {
169            return recordBuilderFactoryProvider.apply(id);
170        }
171        if (ProxyGenerator.class == api) {
172            return proxyGenerator;
173        }
174        if (LocalCache.class == api) {
175            final LocalCacheService service =
176                    new LocalCacheService(id, System::currentTimeMillis, this.executorService);
177            Injector.class.cast(services.get().get(Injector.class)).inject(service);
178            return service;
179        }
180        if (Injector.class == api) {
181            return new InjectorImpl(id, reflections, proxyGenerator, services.get());
182        }
183        if (HttpClientFactory.class == api) {
184            return new HttpClientFactoryImpl(id, reflections, Jsonb.class.cast(services.get().get(Jsonb.class)),
185                    services.get());
186        }
187        if (Resolver.class == api) {
188            return new ResolverImpl(id, resolver);
189        }
190        if (ObjectFactory.class == api) {
191            return new ObjectFactoryImpl(id, propertyEditorRegistry);
192        }
193        if (ProducerFinder.class == api) {
194            final RecordService recordService =
195                    this.lookup(id, loader, localConfigLookup, resolver, RecordService.class, services, instantiators);
196            Iterator<ProducerFinder> producerFinders = ServiceLoader.load(ProducerFinder.class, loader).iterator();
197            if (producerFinders.hasNext()) {
198                ProducerFinder producerFinder = producerFinders.next();
199                if (producerFinders.hasNext()) {
200                    log.warn("More than one ProducerFinder are available via SPI, using {}.",
201                            producerFinder.getClass().getSimpleName());
202                }
203                return producerFinder.init(id, instantiators, recordService::toRecord);
204            } else {
205                return new ProducerFinderImpl().init(id, instantiators, recordService::toRecord);
206            }
207        }
208        if (RecordPointerFactory.class == api) {
209            return new RecordPointerFactoryImpl(id);
210        }
211        if (ContainerInfo.class == api) {
212            return new ContainerInfo(id);
213        }
214        if (RecordService.class == api) {
215            return new RecordServiceImpl(id, recordBuilderFactoryProvider.apply(id), () -> jsonpBuilderFactory,
216                    () -> jsonpProvider,
217                    Lazy
218                            .lazy(() -> Jsonb.class
219                                    .cast(doLookup(id, loader, localConfigLookup, resolver, Jsonb.class, services,
220                                            instantiators))));
221        }
222        return null;
223    }
224
225    private JsonbBuilder createPojoJsonbBuilder(final String id, final Supplier<Jsonb> jsonb) {
226        final JsonbBuilder jsonbBuilder = JsonbBuilder
227                .newBuilder()
228                .withProvider(new PreComputedJsonpProvider(id, jsonpProvider, jsonpParserFactory, jsonpWriterFactory,
229                        jsonpBuilderFactory,
230                        new RecordJsonGenerator.Factory(Lazy.lazy(() -> recordBuilderFactoryProvider.apply(id)), jsonb,
231                                emptyMap()),
232                        jsonpReaderFactory))
233                .withConfig(jsonbConfig);
234        try { // to passthrough the writer, otherwise RecoderJsonGenerator is broken
235            final Field mapper = jsonbBuilder.getClass().getDeclaredField("builder");
236            if (!mapper.isAccessible()) {
237                mapper.setAccessible(true);
238            }
239            MapperBuilder.class.cast(mapper.get(jsonbBuilder)).setDoCloseOnStreams(true);
240        } catch (final Exception e) {
241            throw new IllegalStateException(e);
242        }
243        return jsonbBuilder;
244    }
245
246    private Properties aggregateConfigurations(final Stream<InputStream> localConfigs) {
247        final AtomicReference<RuntimeException> re = new AtomicReference<>();
248        final Properties result = localConfigs.map(stream -> {
249            final Properties properties = new Properties();
250            try {
251                if (stream != null) {
252                    properties.load(stream);
253                }
254                return properties;
255            } catch (final IOException e) {
256                log.error(e.getMessage(), e);
257                RuntimeException runtimeException = re.get();
258                if (runtimeException == null) {
259                    runtimeException = new IllegalStateException("Can't read all local configurations");
260                    re.set(runtimeException);
261                }
262                runtimeException.addSuppressed(e);
263                return properties;
264            } finally {
265                if (stream != null) {
266                    try {
267                        stream.close();
268                    } catch (final IOException e) {
269                        // no-op
270                    }
271                }
272            }
273        })
274                .sorted(comparing(it -> Integer.parseInt(it.getProperty("_ordinal", "0"))))
275                .reduce(new Properties(), (p1, p2) -> {
276                    p1.putAll(p2);
277                    return p1;
278                });
279        final RuntimeException error = re.get();
280        if (error != null) {
281            throw error;
282        }
283        return result;
284    }
285
286    /**
287     * Build executor service
288     * used by
289     * - Local cache for eviction.
290     *
291     * @return scheduled executor service.
292     */
293    private ScheduledExecutorService buildExecutorService() {
294        return Executors.newScheduledThreadPool(4, (Runnable r) -> {
295            final Thread thread = new Thread(r, DefaultServiceProvider.class.getName() + "-services-" + hashCode());
296            thread.setPriority(Thread.NORM_PRIORITY);
297            return thread;
298        });
299    }
300}