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;
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}