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.input;
017
018import static java.util.Collections.emptyMap;
019import static java.util.Optional.ofNullable;
020import static java.util.stream.Collectors.toList;
021
022import java.io.ByteArrayInputStream;
023import java.io.IOException;
024import java.io.InvalidObjectException;
025import java.io.ObjectInputStream;
026import java.io.ObjectStreamException;
027import java.io.Serializable;
028import java.lang.reflect.Method;
029import java.util.Collection;
030import java.util.List;
031import java.util.Map;
032import java.util.function.Function;
033
034import org.talend.sdk.component.api.input.Assessor;
035import org.talend.sdk.component.api.input.Emitter;
036import org.talend.sdk.component.api.input.Split;
037import org.talend.sdk.component.runtime.base.Delegated;
038import org.talend.sdk.component.runtime.base.LifecycleImpl;
039import org.talend.sdk.component.runtime.serialization.ContainerFinder;
040import org.talend.sdk.component.runtime.serialization.EnhancedObjectInputStream;
041
042import lombok.AllArgsConstructor;
043import lombok.extern.slf4j.Slf4j;
044
045@Slf4j
046public class PartitionMapperImpl extends LifecycleImpl implements Mapper, Delegated {
047
048    private static final Object[] NO_ARG = new Object[0];
049
050    private String inputName;
051
052    private boolean stream;
053
054    private transient Method assessor;
055
056    private transient Method split;
057
058    private transient Method inputFactory;
059
060    private transient Function<Long, Object[]> splitArgSupplier;
061
062    private final Map<String, String> internalConfiguration;
063
064    public PartitionMapperImpl(final String rootName, final String name, final String inputName, final String plugin,
065            final boolean stream, final Map<String, String> internalConfiguration, final Serializable instance) {
066        super(instance, rootName, name, plugin);
067        this.stream = stream;
068        this.inputName = inputName;
069        this.internalConfiguration = internalConfiguration;
070        log.debug("[PartitionMapperImpl] {} internalConfig:{}.", name, internalConfiguration);
071    }
072
073    public PartitionMapperImpl(final String rootName, final String name, final String inputName, final String plugin,
074            final boolean stream, final Serializable instance) {
075        this(rootName, name, inputName, plugin, stream, emptyMap(), instance);
076    }
077
078    protected PartitionMapperImpl() {
079        internalConfiguration = emptyMap();
080    }
081
082    @Override
083    public long assess() {
084        lazyInit();
085        if (assessor != null) {
086            return Number.class.cast(doInvoke(assessor)).longValue();
087        }
088        return 1;
089    }
090
091    @Override
092    public List<Mapper> split(final long desiredSize) {
093        lazyInit();
094        return ((Collection<?>) doInvoke(split, splitArgSupplier.apply(desiredSize)))
095                .stream()
096                .map(Serializable.class::cast)
097                .map(mapper -> new PartitionMapperImpl(rootName(), name(), inputName, plugin(), stream,
098                        internalConfiguration, mapper))
099                .collect(toList());
100    }
101
102    @Override
103    public Input create() {
104        lazyInit();
105        // note: we can surely mutualize/cache the reflection a bit here but let's wait
106        // to see it is useful before doing it,
107        // java 7/8 made enough progress to probably make it smooth OOTB
108        final Serializable input = Serializable.class.cast(doInvoke(inputFactory));
109        log.debug("[PartitionMapperImpl#create] isStream? {}.", isStream());
110        if (isStream()) {
111            return new StreamingInputImpl(rootName(), inputName, plugin(), input,
112                    Streaming.loadRetryConfiguration(plugin()),
113                    Streaming.loadStopStrategy(plugin(), internalConfiguration));
114        }
115        return new InputImpl(rootName(), inputName, plugin(), input);
116    }
117
118    @Override
119    public boolean isStream() {
120        return stream;
121    }
122
123    @Override
124    public Object getDelegate() {
125        return delegate;
126    }
127
128    public Map<String, String> getInternalConfiguration() {
129        return ofNullable(internalConfiguration).orElse(emptyMap());
130    }
131
132    private void lazyInit() {
133        if (split == null || inputFactory == null) {
134            inputName = inputName == null || inputName.isEmpty() ? name() : inputName;
135            assessor = findMethods(Assessor.class).findFirst().orElse(null);
136            split = findMethods(Split.class).findFirst().get();
137            inputFactory = findMethods(Emitter.class).findFirst().get();
138
139            switch (split.getParameterCount()) {
140                case 1:
141                    if (int.class == split.getParameterTypes()[0]) {
142                        splitArgSupplier = desiredSize -> new Object[] { desiredSize.intValue() };
143                    } else if (long.class == split.getParameterTypes()[0]) {
144                        splitArgSupplier = desiredSize -> new Object[] { desiredSize };
145                    } else {
146                        throw new IllegalArgumentException("@PartitionSize only supports int and long");
147                    }
148                    break;
149                case 0:
150                default:
151                    splitArgSupplier = desiredSize -> NO_ARG;
152            }
153        }
154    }
155
156    Object writeReplace() throws ObjectStreamException {
157        return new SerializationReplacer(plugin(), rootName(), name(), inputName, stream, serializeDelegate(),
158                internalConfiguration);
159    }
160
161    @AllArgsConstructor
162    private static class SerializationReplacer implements Serializable {
163
164        private final String plugin;
165
166        private final String component;
167
168        private final String name;
169
170        private final String input;
171
172        private final boolean stream;
173
174        private final byte[] value;
175
176        private final Map<String, String> internalConfiguration;
177
178        Object readResolve() throws ObjectStreamException {
179            try {
180                return new PartitionMapperImpl(component, name, input, plugin, stream, internalConfiguration,
181                        loadDelegate());
182            } catch (final IOException | ClassNotFoundException e) {
183                final InvalidObjectException invalidObjectException = new InvalidObjectException(e.getMessage());
184                invalidObjectException.initCause(e);
185                throw invalidObjectException;
186            }
187        }
188
189        private Serializable loadDelegate() throws IOException, ClassNotFoundException {
190            try (final ObjectInputStream ois = new EnhancedObjectInputStream(new ByteArrayInputStream(value),
191                    ContainerFinder.Instance.get().find(plugin).classloader())) {
192                final Object obj = ois.readObject();
193                return Serializable.class.cast(obj);
194            }
195        }
196    }
197}