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.reflect.parameterenricher;
017
018import static java.util.Collections.emptyMap;
019import static java.util.Collections.singletonMap;
020import static java.util.Optional.ofNullable;
021import static java.util.stream.Collectors.joining;
022
023import java.lang.annotation.Annotation;
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Method;
026import java.lang.reflect.Type;
027import java.util.HashMap;
028import java.util.Map;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.ConcurrentMap;
031import java.util.stream.IntStream;
032import java.util.stream.Stream;
033
034import org.talend.sdk.component.api.configuration.action.meta.ActionRef;
035import org.talend.sdk.component.api.service.ActionType;
036
037public class ActionParameterEnricher extends BaseParameterEnricher {
038
039    public static final String META_PREFIX = "tcomp::action::";
040
041    private final ConcurrentMap<String, String> clientActionsMapping = new ConcurrentHashMap<>();
042
043    @Override
044    public Map<String, String> onParameterAnnotation(final String parameterName, final Type parameterType,
045            final Annotation annotation) {
046        final Class<? extends Annotation> annotationType = annotation.annotationType();
047        final ActionRef ref = annotationType.getAnnotation(ActionRef.class);
048        if (ref == null) {
049            return emptyMap();
050        }
051        final Class<?> actionType = ref.value();
052        if (actionType == Object.class) { // client action
053            return singletonMap(
054                    META_PREFIX
055                            + clientActionsMapping.computeIfAbsent(annotationType.getSimpleName(), this::toSnakeCase),
056                    getClientActionName(annotation));
057        }
058        final String type = actionType.getAnnotation(ActionType.class).value();
059        return new HashMap<String, String>() {
060
061            {
062                put(META_PREFIX + type, getValueString(ref.ref(), annotation));
063                ofNullable(getParametersString(annotation)).ifPresent(v -> put(META_PREFIX + type + "::parameters", v));
064                Stream
065                        .of(annotationType.getMethods())
066                        .filter(it -> annotationType == it.getDeclaringClass()
067                                && Stream.of("parameters", "value").noneMatch(v -> it.getName().equalsIgnoreCase(v)))
068                        .forEach(m -> put(META_PREFIX + type + "::" + m.getName(), getString(m, annotation)));
069            }
070        };
071    }
072
073    private String toSnakeCase(final String camelCaseName) {
074        final char[] chars = camelCaseName.substring(1).toCharArray();
075        return Character.toLowerCase(camelCaseName.charAt(0))
076                + IntStream.range(0, chars.length).mapToObj(i -> chars[i]).map(c -> {
077                    if (Character.isUpperCase(c)) {
078                        return "_" + Character.toLowerCase(c);
079                    }
080                    return Character.toString(c);
081                }).collect(joining());
082    }
083
084    private String getClientActionName(final Annotation clientMeta) {
085        final String value = getValueString("value", clientMeta);
086        return "CUSTOM".equalsIgnoreCase(value) ? getValueString("name", clientMeta) : value;
087    }
088
089    private String getString(final Method method, final Annotation annotation) {
090        try {
091            return String.valueOf(method.invoke(annotation));
092        } catch (final IllegalAccessException | InvocationTargetException e) {
093            throw new IllegalArgumentException("No valid " + method + " for " + annotation);
094        }
095    }
096
097    private String getValueString(final String method, final Annotation annotation) {
098        try {
099            return getString(annotation.annotationType().getMethod(method), annotation);
100        } catch (final NoSuchMethodException e) {
101            throw new IllegalArgumentException("No " + method + " for " + annotation);
102        }
103    }
104
105    private String getParametersString(final Annotation annotation) {
106        try {
107            return String
108                    .join(",", String[].class
109                            .cast(annotation.annotationType().getMethod("parameters").invoke(annotation)));
110        } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
111            return null;
112        }
113    }
114}