/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.openmessaging.connect.runtime.utils.datasync; import com.alibaba.fastjson.JSON; import io.openmessaging.Future; import io.openmessaging.Message; import io.openmessaging.MessagingAccessPoint; import io.openmessaging.OMS; import io.openmessaging.OMSBuiltinKeys; import io.openmessaging.connect.runtime.common.LoggerName; import io.openmessaging.connect.runtime.config.RuntimeConfigDefine; import io.openmessaging.connector.api.data.Converter; import io.openmessaging.consumer.PushConsumer; import io.openmessaging.producer.Producer; import io.openmessaging.producer.SendResult; import java.util.Base64; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Broker base data synchronizer, synchronize data between workers. * @param <K> * @param <V> */ public class BrokerBasedLog<K, V> implements DataSynchronizer<K, V>{ private static final Logger log = LoggerFactory.getLogger(LoggerName.OMS_RUNTIME); /** * A callback to receive data from other workers. */ private DataSynchronizerCallback<K, V> dataSynchronizerCallback; /** * Producer to send data to broker. */ private Producer producer; /** * Consumer to receive synchronize data from broker. */ private PushConsumer consumer; /** * A queue to send or consume message. */ private String queueName; /** * Used to convert key to byte[]. */ private Converter keyConverter; /** * Used to convert value to byte[]. */ private Converter valueConverter; public BrokerBasedLog(MessagingAccessPoint messagingAccessPoint, String queueName, String consumerId, DataSynchronizerCallback<K, V> dataSynchronizerCallback, Converter keyConverter, Converter valueConverter){ this.queueName = queueName; this.dataSynchronizerCallback = dataSynchronizerCallback; producer = messagingAccessPoint.createProducer(); consumer = messagingAccessPoint.createPushConsumer( OMS.newKeyValue().put(OMSBuiltinKeys.CONSUMER_ID, consumerId)); this.keyConverter = keyConverter; this.valueConverter = valueConverter; } @Override public void start() { producer.startup(); consumer.attachQueue(queueName, (message, context) -> { try { // Need openMessaging to support start consume message from tail. if(Long.parseLong(message.sysHeaders().getString(Message.BuiltinKeys.BORN_TIMESTAMP)) + 10000 < System.currentTimeMillis()){ context.ack(); return; } log.info("Received one message: {}", message.sysHeaders().getString(Message.BuiltinKeys.MESSAGE_ID) + "\n"); byte[] bytes = message.getBody(byte[].class); Map<K, V> map = decodeKeyValue(bytes); for (K key : map.keySet()) { dataSynchronizerCallback.onCompletion(null, key, map.get(key)); } context.ack(); }catch(Exception e){ log.error("BrokerBasedLog process message failed.", e); } }); consumer.startup(); } @Override public void stop(){ producer.shutdown(); consumer.shutdown(); } @Override public void send(K key, V value){ try { byte[] messageBody = encodeKeyValue(key, value); if (messageBody.length > RuntimeConfigDefine.MAX_MESSAGE_SIZE) { log.error("Message size is greater than {} bytes, key: {}, value {}", RuntimeConfigDefine.MAX_MESSAGE_SIZE, key, value); return; } Future<SendResult> result = producer.sendAsync(producer.createBytesMessage(queueName, messageBody)); result.addListener((future) -> { if (future.getThrowable() != null) { log.error("Send async message Failed, error: {}", future.getThrowable()); } else { log.info("Send async message OK, msgId: {}", future.get().messageId() + "\n"); } }); } catch (Exception e) { log.error("BrokerBaseLog send async message Failed.", e); } } private byte[] encodeKeyValue(K key, V value) throws Exception { byte[] keyByte = keyConverter.objectToByte(key); byte[] valueByte = valueConverter.objectToByte(value); Map<String, String> map = new HashMap<>(); map.put(Base64.getEncoder().encodeToString(keyByte), Base64.getEncoder().encodeToString(valueByte)); return JSON.toJSONString(map).getBytes("UTF-8"); } private Map<K, V> decodeKeyValue(byte[] bytes) throws Exception { Map<K, V> resultMap = new HashMap<>(); String rawString = new String(bytes, "UTF-8"); Map<String, String> map = JSON.parseObject(rawString, Map.class); for(String key : map.keySet()){ K decodeKey = (K)keyConverter.byteToObject(Base64.getDecoder().decode(key)); V decodeValue = (V)valueConverter.byteToObject(Base64.getDecoder().decode(map.get(key))); resultMap.put(decodeKey, decodeValue); } return resultMap; } }