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; 017 018import static java.util.Optional.empty; 019import static java.util.Optional.ofNullable; 020 021import java.lang.reflect.Method; 022import java.util.Collection; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Locale; 026import java.util.Map; 027import java.util.MissingResourceException; 028import java.util.Optional; 029import java.util.ResourceBundle; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.concurrent.ConcurrentMap; 032import java.util.function.Function; 033import java.util.function.Supplier; 034import java.util.stream.Stream; 035 036import org.talend.sdk.component.api.component.MigrationHandler; 037import org.talend.sdk.component.api.processor.ElementListener; 038import org.talend.sdk.component.runtime.base.Lifecycle; 039import org.talend.sdk.component.runtime.input.Mapper; 040import org.talend.sdk.component.runtime.internationalization.ComponentBundle; 041import org.talend.sdk.component.runtime.internationalization.FamilyBundle; 042import org.talend.sdk.component.runtime.manager.util.IdGenerator; 043import org.talend.sdk.component.runtime.output.Processor; 044import org.talend.sdk.component.runtime.standalone.DriverRunner; 045 046import lombok.AccessLevel; 047import lombok.Data; 048import lombok.EqualsAndHashCode; 049import lombok.Getter; 050import lombok.ToString; 051import lombok.extern.slf4j.Slf4j; 052 053@Data 054@Slf4j 055public class ComponentFamilyMeta { 056 057 private static final FamilyBundle NO_COMPONENT_BUNDLE = new FamilyBundle(null, null) { 058 059 @Override 060 public Optional<String> actionDisplayName(final String type, final String name) { 061 return empty(); 062 } 063 064 @Override 065 public Optional<String> category(final String value) { 066 return empty(); 067 } 068 069 @Override 070 public Optional<String> configurationDisplayName(final String type, final String name) { 071 return empty(); 072 } 073 074 @Override 075 public Optional<String> displayName() { 076 return empty(); 077 } 078 }; 079 080 private final String id; 081 082 private final String plugin; 083 084 private final Collection<String> categories; 085 086 private final String icon; 087 088 private final String name; 089 090 private final String packageName; 091 092 private final Map<String, PartitionMapperMeta> partitionMappers = new HashMap<>(); 093 094 private final Map<String, ProcessorMeta> processors = new HashMap<>(); 095 096 private final Map<String, DriverRunnerMeta> driverRunners = new HashMap<>(); 097 098 private final ConcurrentMap<Locale, FamilyBundle> bundles = new ConcurrentHashMap<>(); 099 100 public ComponentFamilyMeta(final String plugin, final Collection<String> categories, final String icon, 101 final String name, final String packageName) { 102 this.id = IdGenerator.get(plugin, name); 103 this.plugin = plugin; 104 this.categories = categories; 105 this.icon = icon; 106 this.name = name; 107 this.packageName = packageName; 108 } 109 110 public FamilyBundle findBundle(final ClassLoader loader, final Locale locale) { 111 return bundles.computeIfAbsent(locale, l -> { 112 try { 113 final ResourceBundle bundle = ResourceBundle 114 .getBundle((packageName.isEmpty() ? packageName : (packageName + '.')) + "Messages", locale, 115 loader); 116 return new FamilyBundle(bundle, name + '.'); 117 } catch (final MissingResourceException mre) { 118 log 119 .warn("No bundle for " + packageName + " (" + name 120 + "), means the display names will be the technical names"); 121 log.debug(mre.getMessage(), mre); 122 return NO_COMPONENT_BUNDLE; 123 } 124 }); 125 } 126 127 @Data 128 public static class BaseMeta<T extends Lifecycle> { 129 130 private static final ComponentBundle NO_COMPONENT_BUNDLE = new ComponentBundle(null, null) { 131 132 @Override 133 public Optional<String> displayName() { 134 return empty(); 135 } 136 }; 137 138 private final ComponentFamilyMeta parent; 139 140 private final String name; 141 142 private final String icon; 143 144 private final int version; 145 146 private final String packageName; 147 148 private final Supplier<MigrationHandler> migrationHandler; 149 150 private final Supplier<List<ParameterMeta>> parameterMetas; 151 152 private final ConcurrentMap<Locale, ComponentBundle> bundles = new ConcurrentHashMap<>(); 153 154 /** 155 * Stores data provided by extensions like ContainerListenerExtension 156 */ 157 @Getter(AccessLevel.NONE) 158 private final ConcurrentMap<Class<?>, Object> extensionsData = new ConcurrentHashMap<>(); 159 160 private final String id; 161 162 private final Class<?> type; 163 164 private final Function<Map<String, String>, T> instantiator; 165 166 private final boolean validated; 167 168 private final Map<String, String> metadata; 169 170 BaseMeta(final ComponentFamilyMeta parent, final String name, final String icon, final int version, 171 final Class<?> type, final Supplier<List<ParameterMeta>> parameterMetas, 172 final Supplier<MigrationHandler> migrationHandler, final Function<Map<String, String>, T> instantiator, 173 final boolean validated, final Map<String, String> metas) { 174 this.parent = parent; 175 this.name = name; 176 this.icon = icon; 177 this.version = version; 178 this.packageName = ofNullable(type.getPackage()).map(Package::getName).orElse(""); 179 this.parameterMetas = parameterMetas; 180 this.migrationHandler = migrationHandler; 181 this.type = type; 182 this.instantiator = instantiator; 183 this.validated = validated; 184 this.metadata = metas; 185 186 this.id = IdGenerator.get(parent.getPlugin(), parent.getName(), name); 187 188 } 189 190 public ComponentBundle findBundle(final ClassLoader loader, final Locale locale) { 191 return bundles.computeIfAbsent(locale, l -> { 192 try { 193 final ResourceBundle bundle = ResourceBundle 194 .getBundle((packageName.isEmpty() ? packageName : (packageName + '.')) + "Messages", locale, 195 loader); 196 return new ComponentBundle(bundle, parent.name + '.' + name + '.'); 197 } catch (final MissingResourceException mre) { 198 log 199 .warn("No bundle for " + packageName + " (" + parent.name + " / " + name 200 + "), means the display names will be the technical names"); 201 log.debug(mre.getMessage(), mre); 202 return NO_COMPONENT_BUNDLE; 203 } 204 }); 205 } 206 207 public T instantiate(final Map<String, String> configuration, final int configVersion) { 208 if (configuration == null) { 209 return this.getInstantiator().apply(null); 210 } 211 final Supplier<MigrationHandler> migrationHandler = this.getMigrationHandler(); 212 final Map<String, String> migratedConfiguration = 213 migrationHandler.get().migrate(configVersion, configuration); 214 return this.getInstantiator().apply(migratedConfiguration); 215 } 216 217 /** 218 * Sets data provided by extension 219 * 220 * @param key {@link Class} of data provided 221 * @param instance data instance 222 * @param <D> the type of the instance to store. 223 * 224 * @return data instance 225 */ 226 public <D> D set(final Class<D> key, final D instance) { 227 return (D) extensionsData.put(key, instance); 228 } 229 230 /** 231 * Returns extension data instance 232 * 233 * @param key {@link Class} of data instance to return 234 * @param <D> the type of the instance to store. 235 * 236 * @return data instance 237 */ 238 public <D> D get(final Class<D> key) { 239 return (D) extensionsData.get(key); 240 } 241 } 242 243 @ToString 244 @EqualsAndHashCode(callSuper = true) 245 public static class PartitionMapperMeta extends BaseMeta<Mapper> { 246 247 public static final String MAPPER_INFINITE = "mapper::infinite"; 248 249 protected PartitionMapperMeta(final ComponentFamilyMeta parent, final String name, final String icon, 250 final int version, final Class<?> type, final Supplier<List<ParameterMeta>> parameterMetas, 251 final Function<Map<String, String>, Mapper> instantiator, 252 final Supplier<MigrationHandler> migrationHandler, final boolean validated, 253 final Map<String, String> metas) { 254 super(parent, name, icon, version, type, parameterMetas, migrationHandler, instantiator, validated, metas); 255 } 256 257 protected PartitionMapperMeta(final ComponentFamilyMeta parent, final String name, final String icon, 258 final int version, final Class<?> type, final Supplier<List<ParameterMeta>> parameterMetas, 259 final Function<Map<String, String>, Mapper> instantiator, 260 final Supplier<MigrationHandler> migrationHandler, final boolean validated, final boolean infinite) { 261 super(parent, name, icon, version, type, parameterMetas, migrationHandler, instantiator, validated, 262 new HashMap<String, String>() { 263 264 { 265 put(MAPPER_INFINITE, Boolean.toString(infinite)); 266 } 267 }); 268 } 269 270 public boolean isInfinite() { 271 return Boolean.parseBoolean(getMetadata().getOrDefault(MAPPER_INFINITE, "false")); 272 } 273 } 274 275 @ToString 276 @EqualsAndHashCode(callSuper = true) 277 public static class ProcessorMeta extends BaseMeta<Processor> { 278 279 protected ProcessorMeta(final ComponentFamilyMeta parent, final String name, final String icon, 280 final int version, final Class<?> type, final Supplier<List<ParameterMeta>> parameterMetas, 281 final Function<Map<String, String>, Processor> instantiator, 282 final Supplier<MigrationHandler> migrationHandler, final boolean validated, 283 final Map<String, String> metas) { 284 super(parent, name, icon, version, type, parameterMetas, migrationHandler, instantiator, validated, metas); 285 } 286 287 /** 288 * Returns {@link Processor} class method annotated with {@link ElementListener} 289 * 290 * @return listener method 291 */ 292 public Method getListener() { 293 return Stream 294 .of(getType().getMethods()) 295 .filter(m -> m.isAnnotationPresent(ElementListener.class)) 296 .findFirst() 297 .orElseThrow(() -> new IllegalArgumentException("No @ElementListener method in " + getType())); 298 } 299 } 300 301 @ToString 302 @EqualsAndHashCode(callSuper = true) 303 public static class DriverRunnerMeta extends BaseMeta<DriverRunner> { 304 305 protected DriverRunnerMeta(final ComponentFamilyMeta parent, final String name, final String icon, 306 final int version, final Class<?> type, final Supplier<List<ParameterMeta>> parameterMetas, 307 final Function<Map<String, String>, DriverRunner> instantiator, 308 final Supplier<MigrationHandler> migrationHandler, final boolean validated, 309 final Map<String, String> metas) { 310 super(parent, name, icon, version, type, parameterMetas, migrationHandler, instantiator, validated, metas); 311 } 312 } 313}