All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.debezium.server.DebeziumServer Maven / Gradle / Ivy

There is a newer version: 3.0.2.Final
Show newest version
/*
 * Copyright Debezium Authors.
 *
 * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
 */
package io.debezium.server;

import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.health.Liveness;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.debezium.DebeziumException;
import io.debezium.engine.ChangeEvent;
import io.debezium.engine.DebeziumEngine;
import io.debezium.engine.DebeziumEngine.ChangeConsumer;
import io.debezium.engine.format.Avro;
import io.debezium.engine.format.CloudEvents;
import io.debezium.engine.format.Json;
import io.debezium.engine.format.Protobuf;
import io.debezium.server.events.ConnectorCompletedEvent;
import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.Startup;

/**
 * 

The entry point of the Quarkus-based standalone server. The server is configured via Quarkus/Microprofile Configuration sources * and provides few out-of-the-box target implementations.

*

The implementation uses CDI to find all classes that implements {@link DebeziumEngine.ChangeConsumer} interface. * The candidate classes should be annotated with {@code @Named} annotation and should be {@code Dependent}.

*

The configuration option {@code debezium.consumer} provides a name of the consumer that should be used and the value * must match to exactly one of the implementation classes.

* * @author Jiri Pechanec * */ @ApplicationScoped @Startup public class DebeziumServer { private static final Logger LOGGER = LoggerFactory.getLogger(DebeziumServer.class); private static final String PROP_PREFIX = "debezium."; private static final String PROP_SOURCE_PREFIX = PROP_PREFIX + "source."; private static final String PROP_SINK_PREFIX = PROP_PREFIX + "sink."; private static final String PROP_FORMAT_PREFIX = PROP_PREFIX + "format."; private static final String PROP_TRANSFORMS_PREFIX = PROP_PREFIX + "transforms."; private static final String PROP_KEY_FORMAT_PREFIX = PROP_FORMAT_PREFIX + "key."; private static final String PROP_VALUE_FORMAT_PREFIX = PROP_FORMAT_PREFIX + "value."; private static final String PROP_TRANSFORMS = PROP_PREFIX + "transforms"; private static final String PROP_SINK_TYPE = PROP_SINK_PREFIX + "type"; private static final String PROP_KEY_FORMAT = PROP_FORMAT_PREFIX + "key"; private static final String PROP_VALUE_FORMAT = PROP_FORMAT_PREFIX + "value"; private static final String PROP_TERMINATION_WAIT = PROP_PREFIX + "termination.wait"; private static final String FORMAT_JSON = Json.class.getSimpleName().toLowerCase(); private static final String FORMAT_CLOUDEVENT = CloudEvents.class.getSimpleName().toLowerCase(); private static final String FORMAT_AVRO = Avro.class.getSimpleName().toLowerCase(); private static final String FORMAT_PROTOBUF = Protobuf.class.getSimpleName().toLowerCase(); private static final Pattern SHELL_PROPERTY_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+_+[a-zA-Z0-9_]+$"); private ExecutorService executor = Executors.newSingleThreadExecutor(); private int returnCode = 0; @Inject BeanManager beanManager; @Inject @Liveness ConnectorLifecycle health; private Bean>> consumerBean; private CreationalContext>> consumerBeanCreationalContext; private DebeziumEngine.ChangeConsumer> consumer; private DebeziumEngine engine; private final Properties props = new Properties(); @SuppressWarnings("unchecked") @PostConstruct public void start() { final Config config = ConfigProvider.getConfig(); final String name = config.getValue(PROP_SINK_TYPE, String.class); final Set> beans = beanManager.getBeans(name).stream() .filter(x -> DebeziumEngine.ChangeConsumer.class.isAssignableFrom(x.getBeanClass())) .collect(Collectors.toSet()); LOGGER.debug("Found {} candidate consumer(s)", beans.size()); if (beans.size() == 0) { throw new DebeziumException("No Debezium consumer named '" + name + "' is available"); } else if (beans.size() > 1) { throw new DebeziumException("Multiple Debezium consumers named '" + name + "' were found"); } consumerBean = (Bean>>) beans.iterator().next(); consumerBeanCreationalContext = beanManager.createCreationalContext(consumerBean); consumer = consumerBean.create(consumerBeanCreationalContext); LOGGER.info("Consumer '{}' instantiated", consumer.getClass().getName()); final Class keyFormat = (Class) getFormat(config, PROP_KEY_FORMAT); final Class valueFormat = (Class) getFormat(config, PROP_VALUE_FORMAT); configToProperties(config, props, PROP_SOURCE_PREFIX, ""); configToProperties(config, props, PROP_FORMAT_PREFIX, "key.converter."); configToProperties(config, props, PROP_FORMAT_PREFIX, "value.converter."); configToProperties(config, props, PROP_KEY_FORMAT_PREFIX, "key.converter."); configToProperties(config, props, PROP_VALUE_FORMAT_PREFIX, "value.converter."); configToProperties(config, props, PROP_SINK_PREFIX, PROP_SINK_PREFIX); final Optional transforms = config.getOptionalValue(PROP_TRANSFORMS, String.class); if (transforms.isPresent()) { props.setProperty("transforms", transforms.get()); configToProperties(config, props, PROP_TRANSFORMS_PREFIX, "transforms."); } props.setProperty("name", name); LOGGER.debug("Configuration for DebeziumEngine: {}", props); engine = DebeziumEngine.create(keyFormat, valueFormat) .using(props) .using((DebeziumEngine.ConnectorCallback) health) .using((DebeziumEngine.CompletionCallback) health) .notifying(consumer) .build(); executor.execute(() -> { try { engine.run(); } finally { Quarkus.asyncExit(returnCode); } }); LOGGER.info("Engine executor started"); } private void configToProperties(Config config, Properties props, String oldPrefix, String newPrefix) { for (String name : config.getPropertyNames()) { String updatedPropertyName = null; if (SHELL_PROPERTY_NAME_PATTERN.matcher(name).matches()) { updatedPropertyName = name.replace("_", ".").toLowerCase(); } if (updatedPropertyName != null && updatedPropertyName.startsWith(oldPrefix)) { props.setProperty(newPrefix + updatedPropertyName.substring(oldPrefix.length()), config.getValue(name, String.class)); } else if (name.startsWith(oldPrefix)) { props.setProperty(newPrefix + name.substring(oldPrefix.length()), config.getConfigValue(name).getValue()); } } } private Class getFormat(Config config, String property) { final String formatName = config.getOptionalValue(property, String.class).orElse(FORMAT_JSON); if (FORMAT_JSON.equals(formatName)) { return Json.class; } else if (FORMAT_CLOUDEVENT.equals(formatName)) { return CloudEvents.class; } else if (FORMAT_AVRO.equals(formatName)) { return Avro.class; } else if (FORMAT_PROTOBUF.equals(formatName)) { return Protobuf.class; } throw new DebeziumException("Unknown format '" + formatName + "' for option " + "'" + property + "'"); } public void stop(@Observes ShutdownEvent event) { try { LOGGER.info("Received request to stop the engine"); final Config config = ConfigProvider.getConfig(); engine.close(); executor.shutdown(); executor.awaitTermination(config.getOptionalValue(PROP_TERMINATION_WAIT, Integer.class).orElse(10), TimeUnit.SECONDS); } catch (Exception e) { LOGGER.error("Exception while shuttting down Debezium", e); } consumerBean.destroy(consumer, consumerBeanCreationalContext); } void connectorCompleted(@Observes ConnectorCompletedEvent event) { if (!event.isSuccess()) { returnCode = 1; } } /** * For test purposes only */ DebeziumEngine.ChangeConsumer getConsumer() { return consumer; } public Properties getProps() { return props; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy