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.tools;
017
018import static java.util.Optional.ofNullable;
019
020import java.lang.annotation.Annotation;
021import java.lang.reflect.Method;
022import java.util.Objects;
023import java.util.Optional;
024import java.util.function.Predicate;
025import java.util.stream.Stream;
026
027import org.talend.sdk.component.api.component.Components;
028import org.talend.sdk.component.api.input.Emitter;
029import org.talend.sdk.component.api.input.PartitionMapper;
030import org.talend.sdk.component.api.processor.Processor;
031import org.talend.sdk.component.api.standalone.DriverRunner;
032
033import lombok.RequiredArgsConstructor;
034
035public class ComponentHelper {
036
037    public static Stream<Class<? extends Annotation>> componentMarkers() {
038        return Stream.of(PartitionMapper.class, Processor.class, Emitter.class, DriverRunner.class);
039    }
040
041    public static Class<?> findPackageOrFail(final Class<?> component, final Predicate<Class<?>> tester,
042            final String api) {
043        final ClassLoader loader = Thread.currentThread().getContextClassLoader();
044        final String pck = component.getPackage() == null ? null : component.getPackage().getName();
045        if (pck != null) {
046            String currentPackage = pck;
047            do {
048                try {
049                    final Class<?> pckInfo = loader.loadClass(currentPackage + ".package-info");
050                    if (tester.test(pckInfo)) {
051                        return pckInfo;
052                    }
053                } catch (final ClassNotFoundException e) {
054                    // no-op
055                }
056
057                final int endPreviousPackage = currentPackage.lastIndexOf('.');
058                if (endPreviousPackage < 0) { // we don't accept default package since it is not specific enough
059                    break;
060                }
061
062                currentPackage = currentPackage.substring(0, endPreviousPackage);
063            } while (true);
064        }
065        throw new IllegalArgumentException("No @" + api + " for the component " + component
066                + ", add it in package-info.java or disable this validation"
067                + " (which can have side effects in integrations/designers)");
068    }
069
070    public static Optional<Component> components(final Class<?> component) {
071        return ComponentHelper
072                .componentMarkers()
073                .map(component::getAnnotation)
074                .filter(Objects::nonNull)
075                .findFirst()
076                .map(ComponentHelper::asComponent);
077    }
078
079    public static String findFamily(final Component c, final Class<?> pckMarker) {
080        return ofNullable(c)
081                .map(Component::family)
082                .filter(name -> !name.isEmpty())
083                .orElseGet(() -> ComponentHelper
084                        .findPackageOrFail(pckMarker, (Class<?> clazz) -> clazz.isAnnotationPresent(Components.class),
085                                Components.class.getName())
086                        .getAnnotation(Components.class)
087                        .family());
088    }
089
090    public static Component asComponent(final Annotation a) {
091        final String family = ComponentHelper.get(a, "family");
092        final String name = ComponentHelper.get(a, "name");
093
094        return new Component(family, name);
095    }
096
097    private static String get(final Annotation a, final String methodName) {
098        try {
099            final Method getter = a.annotationType().getMethod(methodName);
100            return (String) getter.invoke(a);
101        } catch (ReflectiveOperationException ex) {
102            throw new RuntimeException("Can' find " + methodName + " on annotation " + a.annotationType().getName(),
103                    ex);
104        }
105    }
106
107    @RequiredArgsConstructor
108    public static class Component {
109
110        private final String family;
111
112        private final String name;
113
114        public String family() {
115            return family;
116        }
117
118        public String name() {
119            return name;
120        }
121    }
122}