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}