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.Optional.ofNullable; 019 020import java.io.ByteArrayInputStream; 021import java.io.IOException; 022import java.io.InvalidObjectException; 023import java.io.ObjectInputStream; 024import java.io.ObjectStreamException; 025import java.io.Serializable; 026import java.lang.reflect.Method; 027import java.util.function.Consumer; 028 029import javax.json.bind.Jsonb; 030 031import org.talend.sdk.component.api.component.Version; 032import org.talend.sdk.component.api.input.Producer; 033import org.talend.sdk.component.api.input.checkpoint.CheckpointAvailable; 034import org.talend.sdk.component.api.input.checkpoint.CheckpointData; 035import org.talend.sdk.component.api.service.record.RecordBuilderFactory; 036import org.talend.sdk.component.runtime.base.Delegated; 037import org.talend.sdk.component.runtime.base.LifecycleImpl; 038import org.talend.sdk.component.runtime.record.RecordConverters; 039import org.talend.sdk.component.runtime.serialization.ContainerFinder; 040import org.talend.sdk.component.runtime.serialization.EnhancedObjectInputStream; 041import org.talend.sdk.component.runtime.serialization.LightContainer; 042 043import lombok.AllArgsConstructor; 044import lombok.extern.slf4j.Slf4j; 045 046@Slf4j 047public class InputImpl extends LifecycleImpl implements Input, Delegated { 048 049 private transient Method next; 050 051 private transient RecordConverters converters; 052 053 private transient RecordConverters.MappingMetaRegistry registry; 054 055 private transient Jsonb jsonb; 056 057 private transient RecordBuilderFactory recordBuilderFactory; 058 059 private transient Method checkpoint; 060 061 private transient Method shouldCheckpoint; 062 063 private transient Consumer<CheckpointState> checkpointCallback; 064 065 private boolean checkpointEnabled = Boolean.parseBoolean(System.getProperty("talend.checkpoint.enabled", "false")); 066 067 public InputImpl(final String rootName, final String name, final String plugin, final Serializable instance) { 068 super(instance, rootName, name, plugin); 069 } 070 071 protected InputImpl() { 072 // no-op 073 } 074 075 @Override 076 public void start() { 077 super.start(); 078 initCheckpointFunctions(); 079 } 080 081 @Override 082 public void start(final Consumer<CheckpointState> checkpointCallback) { 083 start(); 084 if (checkpointEnabled) { 085 this.checkpointCallback = checkpointCallback; 086 } 087 } 088 089 protected void initCheckpointFunctions() { 090 if (checkpointEnabled) { 091 checkpoint = findMethods(CheckpointData.class).findFirst().orElse(null); 092 shouldCheckpoint = findMethods(CheckpointAvailable.class).findFirst().orElse(null); 093 } 094 } 095 096 @Override 097 public Object next() { 098 if (next == null) { 099 init(); 100 } 101 final Object data = readNext(); 102 if (data == null) { 103 return null; 104 } 105 // do we need to checkpoint here? 106 if (isCheckpointReady() && checkpointCallback != null) { 107 checkpointCallback.accept(getCheckpoint()); 108 } 109 final Class<?> recordClass = data.getClass(); 110 if (recordClass.isPrimitive() || String.class == recordClass) { 111 // mainly for tests, can be dropped while build is green 112 return data; 113 } 114 return converters.toRecord(registry, data, this::jsonb, this::recordBuilderFactory); 115 } 116 117 @Override 118 public CheckpointState getCheckpoint() { 119 if (checkpoint != null) { 120 Object state = doInvoke(this.checkpoint); 121 int version = 1; 122 if (ofNullable(state.getClass().getAnnotation(Version.class)).isPresent()) { 123 version = state.getClass().getAnnotation(Version.class).value(); 124 } 125 return new CheckpointState(version, state); 126 } 127 return null; 128 } 129 130 @Override 131 public boolean isCheckpointReady() { 132 boolean checked = checkpointEnabled; 133 if (shouldCheckpoint != null) { 134 checked = (Boolean) doInvoke(this.shouldCheckpoint); 135 } 136 return checked; 137 } 138 139 @Override 140 public void stop() { 141 // do we need to checkpoint here? 142 if (checkpointCallback != null) { 143 checkpointCallback.accept(getCheckpoint()); 144 } 145 // 146 super.stop(); 147 } 148 149 @Override 150 public Object getDelegate() { 151 return delegate; 152 } 153 154 protected Object readNext() { 155 return doInvoke(this.next); 156 } 157 158 protected void init() { 159 next = findMethods(Producer.class).findFirst().get(); 160 converters = new RecordConverters(); 161 registry = new RecordConverters.MappingMetaRegistry(); 162 } 163 164 private Jsonb jsonb() { 165 if (jsonb != null) { 166 return jsonb; 167 } 168 synchronized (this) { 169 if (jsonb == null) { 170 final LightContainer container = ContainerFinder.Instance.get().find(plugin()); 171 jsonb = container.findService(Jsonb.class); 172 } 173 } 174 return jsonb; 175 } 176 177 private RecordBuilderFactory recordBuilderFactory() { 178 if (recordBuilderFactory != null) { 179 return recordBuilderFactory; 180 } 181 synchronized (this) { 182 if (recordBuilderFactory == null) { 183 final LightContainer container = ContainerFinder.Instance.get().find(plugin()); 184 recordBuilderFactory = container.findService(RecordBuilderFactory.class); 185 } 186 } 187 return recordBuilderFactory; 188 } 189 190 protected Object writeReplace() throws ObjectStreamException { 191 return new SerializationReplacer(plugin(), rootName(), name(), serializeDelegate()); 192 } 193 194 @AllArgsConstructor 195 protected static class SerializationReplacer implements Serializable { 196 197 protected String plugin; 198 199 protected String component; 200 201 protected String name; 202 203 protected byte[] value; 204 205 protected Object readResolve() throws ObjectStreamException { 206 try { 207 return new InputImpl(component, name, plugin, loadDelegate()); 208 } catch (final IOException | ClassNotFoundException e) { 209 final InvalidObjectException invalidObjectException = new InvalidObjectException(e.getMessage()); 210 invalidObjectException.initCause(e); 211 throw invalidObjectException; 212 } 213 } 214 215 protected Serializable loadDelegate() throws IOException, ClassNotFoundException { 216 try (final ObjectInputStream ois = new EnhancedObjectInputStream(new ByteArrayInputStream(value), 217 ContainerFinder.Instance.get().find(plugin).classloader())) { 218 return Serializable.class.cast(ois.readObject()); 219 } 220 } 221 } 222}