Added debezium-core and MySQL binary log reading tests.
This commit is contained in:
parent
42926f17f3
commit
dffdfd8049
54
debezium-core/pom.xml
Normal file
54
debezium-core/pom.xml
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2014 Red Hat, Inc. and/or its affiliates.
|
||||
~
|
||||
~ Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-parent</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>debezium-core</artifactId>
|
||||
<name>Debezium Core</name>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easytesting</groupId>
|
||||
<artifactId>fest-assert</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<resources>
|
||||
<!-- Apply the properties set in the POM to the resource files -->
|
||||
<resource>
|
||||
<filtering>true</filtering>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>**/build.properties</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,28 @@
|
||||
package io.debezium.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2005 Brian Goetz and Tim Peierls.<br />
|
||||
* Released under the Creative Commons Attribution License<br />
|
||||
* (http://creativecommons.org/licenses/by/2.5)<br />
|
||||
* Official home: http://www.jcip.net<br />
|
||||
* Adopted from Java Concurrency in Practice.
|
||||
* <p>
|
||||
* An annotation that describes the monitor protecting the annotated field or method. For example, <code>@GuardedBy("this")</code>
|
||||
* specifies that the lock is the object in whose class the field or method is defined, while <code>@GuardedBy("lock")</code>
|
||||
* specifies that the method or field is guarded by a lock held in the "lock" field.
|
||||
* </p>
|
||||
*
|
||||
* @see ThreadSafe
|
||||
* @see NotThreadSafe
|
||||
* @see Immutable
|
||||
*/
|
||||
@Target( {ElementType.FIELD, ElementType.METHOD} )
|
||||
@Retention( RetentionPolicy.SOURCE )
|
||||
public @interface GuardedBy {
|
||||
String value();
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package io.debezium.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2005 Brian Goetz and Tim Peierls.<br />
|
||||
* Released under the Creative Commons Attribution License<br />
|
||||
* (http://creativecommons.org/licenses/by/2.5)<br />
|
||||
* Official home: http://www.jcip.net<br />
|
||||
* Adopted from Java Concurrency in Practice.
|
||||
* <p>
|
||||
* This annotation documents that instances of the annotated class are immutable. This means that its state is seen to others as
|
||||
* never being changed, even though the actual private internal state may indeed change. Therefore, in an immutable class:
|
||||
* <ul>
|
||||
* <li>all public fields are final; and</li>
|
||||
* <li>all public final reference fields refer to other immutable objects; and</li>
|
||||
* <li>constructors and methods do not publish references to any internal state which is potentially mutable by the
|
||||
* implementation.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
@Documented
|
||||
@Target( ElementType.TYPE )
|
||||
@Retention( RetentionPolicy.RUNTIME )
|
||||
public @interface Immutable {
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.debezium.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2005 Brian Goetz and Tim Peierls.<br />
|
||||
* Released under the Creative Commons Attribution License<br />
|
||||
* (http://creativecommons.org/licenses/by/2.5)<br />
|
||||
* Official home: http://www.jcip.net<br />
|
||||
* Adopted from Java Concurrency in Practice.
|
||||
* <p>
|
||||
* This annotation documents the class as <i>not</i> being thread-safe, meaning the caller is expected to properly handle and
|
||||
* guard all concurrent operations on an instance.
|
||||
* </p>
|
||||
*
|
||||
* @see ThreadSafe
|
||||
*/
|
||||
@Documented
|
||||
@Target( ElementType.TYPE )
|
||||
@Retention( RetentionPolicy.RUNTIME )
|
||||
public @interface NotThreadSafe {
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.debezium.annotation;
|
||||
|
||||
import static java.lang.annotation.ElementType.CONSTRUCTOR;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PACKAGE;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation that can be used to specify that the target field, method, constructor, package or type is read-only.
|
||||
*
|
||||
* @see Immutable
|
||||
*/
|
||||
@Documented
|
||||
@Retention( RUNTIME )
|
||||
@Target( {FIELD, METHOD, CONSTRUCTOR, PACKAGE, TYPE} )
|
||||
public @interface ReadOnly {
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.debezium.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2005 Brian Goetz and Tim Peierls.<br />
|
||||
* Released under the Creative Commons Attribution License<br />
|
||||
* (http://creativecommons.org/licenses/by/2.5)<br />
|
||||
* Official home: http://www.jcip.net<br />
|
||||
* Adopted from Java Concurrency in Practice.
|
||||
* <p>
|
||||
* This annotation documents the class as being thread-safe. This means that no sequences of accesses (reads and writes to public
|
||||
* fields, calls to public methods) may put the object into an invalid state, regardless of the interleaving of those actions by
|
||||
* the runtime, and without requiring any additional synchronization or coordination on the part of the caller.
|
||||
* </p>
|
||||
*
|
||||
* @see NotThreadSafe
|
||||
*/
|
||||
@Documented
|
||||
@Target( {ElementType.TYPE, ElementType.FIELD} )
|
||||
@Retention( RetentionPolicy.RUNTIME )
|
||||
public @interface ThreadSafe {
|
||||
}
|
@ -0,0 +1,694 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
import io.debezium.util.Collect;
|
||||
import io.debezium.util.IoUtil;
|
||||
|
||||
/**
|
||||
* An immutable representation of a Debezium configuration. A {@link Configuration} instance can be obtained
|
||||
* {@link #from(Properties) from Properties} or loaded from a {@link #load(File) file}, {@link #load(InputStream) stream},
|
||||
* {@link #load(Reader) reader}, {@link #load(URL) URL}, or from a {@link #load(String, ClassLoader) resource on the classpath}.
|
||||
* <p>
|
||||
* A Configuration object is basically a decorator around a {@link Properties} object. It has methods to get and convert
|
||||
* individual property values to numeric, boolean and String types, optionally using a default value if the given property value
|
||||
* does not exist. However, it is {@link Immutable immutable}, so it does not have any methods to set property values, allowing
|
||||
* it to be passed around and reused without concern that other components might change the underlying property values.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@Immutable
|
||||
public interface Configuration {
|
||||
|
||||
/**
|
||||
* The basic interface for configuration builders.
|
||||
* @param <C> the type of configuration
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
public static interface ConfigBuilder<C extends Configuration, B extends ConfigBuilder<C,B>> {
|
||||
/**
|
||||
* Associated the given value with the specified key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return this builder object so methods can be chained together; never null
|
||||
*/
|
||||
B with( String key, String value );
|
||||
/**
|
||||
* Associated the given value with the specified key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return this builder object so methods can be chained together; never null
|
||||
*/
|
||||
default B with( String key, int value ) {
|
||||
return with(key, Integer.toString(value));
|
||||
}
|
||||
/**
|
||||
* Associated the given value with the specified key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return this builder object so methods can be chained together; never null
|
||||
*/
|
||||
default B with( String key, float value ) {
|
||||
return with(key, Float.toString(value));
|
||||
}
|
||||
/**
|
||||
* Associated the given value with the specified key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return this builder object so methods can be chained together; never null
|
||||
*/
|
||||
default B with( String key, double value ) {
|
||||
return with(key, Double.toString(value));
|
||||
}
|
||||
/**
|
||||
* Associated the given value with the specified key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return this builder object so methods can be chained together; never null
|
||||
*/
|
||||
default B with( String key, long value ) {
|
||||
return with(key, Long.toString(value));
|
||||
}
|
||||
/**
|
||||
* Associated the given value with the specified key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return this builder object so methods can be chained together; never null
|
||||
*/
|
||||
default B with( String key, boolean value ) {
|
||||
return with(key, Boolean.toString(value));
|
||||
}
|
||||
/**
|
||||
* Build and return the immutable configuration.
|
||||
* @return the immutable configuration; never null
|
||||
*/
|
||||
C build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of Configuration objects.
|
||||
*/
|
||||
public static class Builder implements ConfigBuilder<Configuration,Builder> {
|
||||
private final Properties props = new Properties();
|
||||
protected Builder() {
|
||||
}
|
||||
protected Builder( Properties props ) {
|
||||
this.props.putAll(props);
|
||||
}
|
||||
@Override
|
||||
public Builder with(String key, String value) {
|
||||
props.setProperty(key, value);
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public Configuration build() {
|
||||
return Configuration.from(props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder configuration builder}.
|
||||
* @return the configuration builder
|
||||
*/
|
||||
public static Builder create() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder configuration builder} that starts with a copy of the supplied configuration.
|
||||
* @param config the configuration to copy
|
||||
* @return the configuration builder
|
||||
*/
|
||||
public static Builder copy( Configuration config) {
|
||||
return new Builder(config.asProperties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Configuration object that is populated by system properties, per {@link #withSystemProperties(String)}.
|
||||
* @param prefix the required prefix for the system properties; may not be null but may be empty
|
||||
* @return the configuration
|
||||
*/
|
||||
public static Configuration fromSystemProperties( String prefix ) {
|
||||
return empty().withSystemProperties(prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an empty configuration.
|
||||
*
|
||||
* @return an empty configuration; never null
|
||||
*/
|
||||
public static Configuration empty() {
|
||||
return new Configuration() {
|
||||
@Override
|
||||
public Set<String> keys() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by copying the supplied Properties object. The supplied {@link Properties} object is
|
||||
* copied so that the resulting Configuration cannot be modified.
|
||||
*
|
||||
* @param properties the properties; may be null or empty
|
||||
* @return the configuration; never null
|
||||
*/
|
||||
public static Configuration from(Properties properties) {
|
||||
Properties props = new Properties();
|
||||
if (properties != null) props.putAll(properties);
|
||||
return new Configuration() {
|
||||
@Override
|
||||
public String getString(String key) {
|
||||
return properties.getProperty(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keys() {
|
||||
return properties.stringPropertyNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return props.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by loading the Properties from the supplied URL.
|
||||
*
|
||||
* @param url the URL to the stream containing the configuration properties; may not be null
|
||||
* @return the configuration; never null
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public static Configuration load(URL url) throws IOException {
|
||||
try (InputStream stream = url.openStream()) {
|
||||
return load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by loading the Properties from the supplied file.
|
||||
*
|
||||
* @param file the file containing the configuration properties; may not be null
|
||||
* @return the configuration; never null
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public static Configuration load(File file) throws IOException {
|
||||
try (InputStream stream = new FileInputStream(file)) {
|
||||
return load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by loading the Properties from the supplied stream.
|
||||
*
|
||||
* @param stream the stream containing the properties; may not be null
|
||||
* @return the configuration; never null
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public static Configuration load(InputStream stream) throws IOException {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(stream);
|
||||
return from(properties);
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by loading the Properties from the supplied reader.
|
||||
*
|
||||
* @param reader the reader containing the properties; may not be null
|
||||
* @return the configuration; never null
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public static Configuration load(Reader reader) throws IOException {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(reader);
|
||||
return from(properties);
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by loading the Properties from a file on the file system or classpath given by the supplied
|
||||
* path.
|
||||
*
|
||||
* @param path the path to the file containing the configuration properties; may not be null
|
||||
* @param clazz the class whose classpath is to be used to find the file; may be null
|
||||
* @return the configuration; never null but possibly empty
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public static Configuration load(String path, Class<?> clazz) throws IOException {
|
||||
return load(path, clazz.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by loading the Properties from a file on the file system or classpath given by the supplied
|
||||
* path.
|
||||
*
|
||||
* @param path the path to the file containing the configuration properties; may not be null
|
||||
* @param classLoader the class loader to use; may be null
|
||||
* @return the configuration; never null but possibly empty
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public static Configuration load(String path, ClassLoader classLoader) throws IOException {
|
||||
Logger logger = LoggerFactory.getLogger(Configuration.class);
|
||||
return load(path, classLoader, logger::debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a configuration instance by loading the Properties from a file on the file system or classpath given by the supplied
|
||||
* path.
|
||||
*
|
||||
* @param path the path to the file containing the configuration properties; may not be null
|
||||
* @param classLoader the class loader to use; may be null
|
||||
* @param logger the function that will be called with status updates; may be null
|
||||
* @return the configuration; never null but possibly empty
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public static Configuration load(String path, ClassLoader classLoader, Consumer<String> logger) throws IOException {
|
||||
try (InputStream stream = IoUtil.getResourceAsStream(path, classLoader, null, null, logger)) {
|
||||
Properties props = new Properties();
|
||||
if (stream != null) {
|
||||
props.load(stream);
|
||||
}
|
||||
return from(props);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this configuration contains a key-value pair with the given key and the value is non-null
|
||||
*
|
||||
* @param key the key
|
||||
* @return true if the configuration contains the key, or false otherwise
|
||||
*/
|
||||
default boolean hasKey(String key) {
|
||||
return getString(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of keys in this configuration.
|
||||
*
|
||||
* @return the set of keys; never null but possibly empty
|
||||
*/
|
||||
public Set<String> keys();
|
||||
|
||||
/**
|
||||
* Get the string value associated with the given key.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @return the value, or null if the key is null or there is no such key-value pair in the configuration
|
||||
*/
|
||||
public String getString(String key);
|
||||
|
||||
/**
|
||||
* Get the string value associated with the given key, returning the default value if there is no such key-value pair.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValue the value that should be returned by default if there is no such key-value pair in the configuration;
|
||||
* may be null
|
||||
* @return the configuration value, or the {@code defaultValue} if there is no such key-value pair in the configuration
|
||||
*/
|
||||
default String getString(String key, String defaultValue) {
|
||||
return getString(key, () -> defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string value associated with the given key, returning the default value if there is no such key-value pair.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValueSupplier the supplier of value that should be returned by default if there is no such key-value pair in
|
||||
* the configuration; may be null and may return null
|
||||
* @return the configuration value, or the {@code defaultValue} if there is no such key-value pair in the configuration
|
||||
*/
|
||||
default String getString(String key, Supplier<String> defaultValueSupplier) {
|
||||
String value = getString(key);
|
||||
return value != null ? value : (defaultValueSupplier != null ? defaultValueSupplier.get() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string value(s) associated with the given key, where the supplied regular expression is used to parse the single
|
||||
* string value into multiple values.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param regex the delimiting regular expression
|
||||
* @return the list of string values; null only if there is no such key-value pair in the configuration
|
||||
* @see String#split(String)
|
||||
*/
|
||||
default List<String> getStrings(String key, String regex) {
|
||||
String value = getString(key);
|
||||
if (value == null) return null;
|
||||
return Collect.arrayListOf(value.split(regex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integer value associated with the given key.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @return the integer value, or null if the key is null, there is no such key-value pair in the configuration, or the value
|
||||
* could not be parsed as an integer
|
||||
*/
|
||||
default Integer getInteger(String key) {
|
||||
return getInteger(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the long value associated with the given key.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @return the integer value, or null if the key is null, there is no such key-value pair in the configuration, or the value
|
||||
* could not be parsed as an integer
|
||||
*/
|
||||
default Long getLong(String key) {
|
||||
return getLong(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the boolean value associated with the given key.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @return the boolean value, or null if the key is null, there is no such key-value pair in the configuration, or the value
|
||||
* could not be parsed as a boolean value
|
||||
*/
|
||||
default Boolean getBoolean(String key) {
|
||||
return getBoolean(key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integer value associated with the given key, returning the default value if there is no such key-value pair or
|
||||
* if the value could not be {@link Integer#parseInt(String) parsed} as an integer.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValue the default value
|
||||
* @return the integer value, or null if the key is null, there is no such key-value pair in the configuration, or the value
|
||||
* could not be parsed as an integer
|
||||
*/
|
||||
default int getInteger(String key, int defaultValue) {
|
||||
return getInteger(key, () -> defaultValue).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the long value associated with the given key, returning the default value if there is no such key-value pair or
|
||||
* if the value could not be {@link Long#parseLong(String) parsed} as a long.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValue the default value
|
||||
* @return the long value, or null if the key is null, there is no such key-value pair in the configuration, or the value
|
||||
* could not be parsed as a long
|
||||
*/
|
||||
default long getLong(String key, long defaultValue) {
|
||||
return getLong(key, () -> defaultValue).longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the boolean value associated with the given key, returning the default value if there is no such key-value pair or
|
||||
* if the value could not be {@link Boolean#parseBoolean(String) parsed} as a boolean value.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValue the default value
|
||||
* @return the boolean value, or null if the key is null, there is no such key-value pair in the configuration, or the value
|
||||
* could not be parsed as a boolean value
|
||||
*/
|
||||
default boolean getBoolean(String key, boolean defaultValue) {
|
||||
return getBoolean(key, () -> defaultValue).booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integer value associated with the given key, using the given supplier to obtain a default value if there is no such
|
||||
* key-value pair.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValueSupplier the supplier for the default value; may be null
|
||||
* @return the integer value, or null if the key is null, there is no such key-value pair in the configuration, the
|
||||
* {@code defaultValueSupplier} reference is null, or there is a key-value pair in the configuration but the value
|
||||
* could not be parsed as an integer
|
||||
*/
|
||||
default Integer getInteger(String key, IntSupplier defaultValueSupplier) {
|
||||
String value = getString(key);
|
||||
if (value != null) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
return defaultValueSupplier != null ? defaultValueSupplier.getAsInt() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the long value associated with the given key, using the given supplier to obtain a default value if there is no such
|
||||
* key-value pair.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValueSupplier the supplier for the default value; may be null
|
||||
* @return the long value, or null if the key is null, there is no such key-value pair in the configuration, the
|
||||
* {@code defaultValueSupplier} reference is null, or there is a key-value pair in the configuration but the value
|
||||
* could not be parsed as a long
|
||||
*/
|
||||
default Long getLong(String key, LongSupplier defaultValueSupplier) {
|
||||
String value = getString(key);
|
||||
if (value != null) {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
return defaultValueSupplier != null ? defaultValueSupplier.getAsLong() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the boolean value associated with the given key, using the given supplier to obtain a default value if there is no such
|
||||
* key-value pair.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param defaultValueSupplier the supplier for the default value; may be null
|
||||
* @return the boolean value, or null if the key is null, there is no such key-value pair in the configuration, the
|
||||
* {@code defaultValueSupplier} reference is null, or there is a key-value pair in the configuration but the value
|
||||
* could not be parsed as a boolean value
|
||||
*/
|
||||
default Boolean getBoolean(String key, BooleanSupplier defaultValueSupplier) {
|
||||
String value = getString(key);
|
||||
if (value != null) {
|
||||
value = value.trim().toLowerCase();
|
||||
if (Boolean.valueOf(value)) return Boolean.TRUE;
|
||||
if (value.equals("false")) return false;
|
||||
}
|
||||
return defaultValueSupplier != null ? defaultValueSupplier.getAsBoolean() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the class given by the value in the configuration associated with the given key.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param clazz the Class of which the resulting object is expected to be an instance of; may not be null
|
||||
* @return the new instance, or null if there is no such key-value pair in the configuration or if there is a key-value
|
||||
* configuration but the value could not be converted to an existing class with a zero-argument constructor
|
||||
*/
|
||||
default <T> T getInstance(String key, Class<T> clazz) {
|
||||
return getInstance(key, clazz, () -> getClass().getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the class given by the value in the configuration associated with the given key.
|
||||
*
|
||||
* @param key the key for the configuration property
|
||||
* @param clazz the Class of which the resulting object is expected to be an instance of; may not be null
|
||||
* @param classloaderSupplier the supplier of the ClassLoader to be used to load the resulting class; may be null if this
|
||||
* class' ClassLoader should be used
|
||||
* @return the new instance, or null if there is no such key-value pair in the configuration or if there is a key-value
|
||||
* configuration but the value could not be converted to an existing class with a zero-argument constructor
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T getInstance(String key, Class<T> clazz, Supplier<ClassLoader> classloaderSupplier) {
|
||||
String className = getString(key);
|
||||
if (className != null) {
|
||||
ClassLoader classloader = classloaderSupplier != null ? classloaderSupplier.get() : getClass().getClassLoader();
|
||||
try {
|
||||
return (T) classloader.loadClass(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link Configuration} that contains only the subset of keys that match the given prefix.
|
||||
* If desired, the keys in the resulting Configuration will have the prefix (plus any terminating "{@code .}" character if
|
||||
* needed) removed.
|
||||
* <p>
|
||||
* This method returns this Configuration instance if the supplied {@code prefix} is null or empty.
|
||||
*
|
||||
* @param prefix the prefix
|
||||
* @param removePrefix true if the prefix (and any subsequent "{@code .}" character) should be removed from the keys in the
|
||||
* resulting Configuration, or false if the keys in this Configuration should be used as-is in the resulting
|
||||
* Configuration
|
||||
* @return the subset of this Configuration; never null
|
||||
*/
|
||||
default Configuration subset(String prefix, boolean removePrefix) {
|
||||
if (prefix == null) return this;
|
||||
prefix = prefix.trim();
|
||||
if (prefix.isEmpty()) return this;
|
||||
String prefixWithSeparator = prefix.endsWith(".") ? prefix : prefix + ".";
|
||||
int minLength = prefixWithSeparator.length();
|
||||
Function<String, String> prefixRemover = removePrefix ? key -> key.substring(minLength) : key -> key;
|
||||
return filter(key->key.startsWith(prefixWithSeparator)).map(prefixRemover);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link Configuration} that contains only the subset of keys that satisfy the given predicate.
|
||||
*
|
||||
* @param mapper that function that transforms keys
|
||||
* @return the subset Configuration; never null
|
||||
*/
|
||||
default Configuration map(Function<String, String> mapper) {
|
||||
if (mapper == null) return this;
|
||||
Map<String,String> newToOld = new HashMap<>();
|
||||
keys().stream().filter(k -> k != null).forEach(oldKey -> {
|
||||
String newKey = mapper.apply(oldKey);
|
||||
if (newKey != null) {
|
||||
newToOld.put(newKey, oldKey);
|
||||
}
|
||||
});
|
||||
return new Configuration() {
|
||||
@Override
|
||||
public Set<String> keys() {
|
||||
return Collect.unmodifiableSet(newToOld.keySet());
|
||||
}
|
||||
@Override
|
||||
public String getString(String key) {
|
||||
String oldKey = newToOld.get(key);
|
||||
return Configuration.this.getString(oldKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link Configuration} that contains only the subset of keys that satisfy the given predicate.
|
||||
*
|
||||
* @param matcher the function that determines whether a key should be included in the subset
|
||||
* @return the subset Configuration; never null
|
||||
*/
|
||||
default Configuration filter(Predicate<? super String> matcher) {
|
||||
if (matcher == null) return this;
|
||||
return new Configuration() {
|
||||
@Override
|
||||
public Set<String> keys() {
|
||||
return Collect.unmodifiableSet(Configuration.this.keys().stream()
|
||||
.filter(k -> k != null)
|
||||
.filter(matcher)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key) {
|
||||
return matcher.test(key) ? Configuration.this.getString(key) : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this configuration is empty and has no properties.
|
||||
*
|
||||
* @return {@code true} if empty, or {@code false} otherwise
|
||||
*/
|
||||
default boolean isEmpty() {
|
||||
return keys().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a copy of these configuration properties as a Properties object.
|
||||
*
|
||||
* @return the properties object; never null
|
||||
*/
|
||||
default Properties asProperties() {
|
||||
Properties props = new Properties();
|
||||
keys().forEach(key -> {
|
||||
String value = getString(key);
|
||||
if (key != null && value != null) props.setProperty(key, value);
|
||||
});
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this configuration except where acceptable system properties are used to overwrite properties copied from
|
||||
* this configuration. All system properties whose name has the given prefix are added, where the prefix is removed from the
|
||||
* system property name, it is converted to lower case, and each underscore character ('{@code _}') are replaced with a
|
||||
* period ('{@code .}').
|
||||
*
|
||||
* @param prefix the required prefix for the system properties
|
||||
* @return the resulting properties converted from the system properties; never null, but possibly empty
|
||||
*/
|
||||
default Configuration withSystemProperties(String prefix) {
|
||||
int prefixLength = prefix.length();
|
||||
return withSystemProperties(input -> {
|
||||
if (input.startsWith(prefix)) {
|
||||
String withoutPrefix = input.substring(prefixLength).trim();
|
||||
if (withoutPrefix.length() > 0) {
|
||||
// Convert to a properties format ...
|
||||
return withoutPrefix.toLowerCase().replaceAll("[_]", ".");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this configuration except where acceptable system properties are used to overwrite properties copied from
|
||||
* this configuration. Each of the system properties is examined and passed to the supplied function; if the result of the
|
||||
* function is a non-null string, then a property with that string as the name and the system property value are added to the
|
||||
* returned configuration.
|
||||
*
|
||||
* @param propertyNameConverter the function that will convert the name of each system property to an applicable property name
|
||||
* (or null if the system property name does not apply); may not be null
|
||||
* @return the resulting properties filtered from the input properties; never null, but possibly empty
|
||||
*/
|
||||
default Configuration withSystemProperties(Function<String, String> propertyNameConverter) {
|
||||
Properties props = asProperties();
|
||||
Properties systemProperties = System.getProperties();
|
||||
for (String key : systemProperties.stringPropertyNames()) {
|
||||
String propName = propertyNameConverter.apply(key);
|
||||
if (propName != null && propName.length() > 0) {
|
||||
String value = systemProperties.getProperty(key);
|
||||
props.setProperty(propName, value);
|
||||
}
|
||||
}
|
||||
return from(props);
|
||||
}
|
||||
}
|
96
debezium-core/src/main/java/io/debezium/crdt/CRDT.java
Normal file
96
debezium-core/src/main/java/io/debezium/crdt/CRDT.java
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
|
||||
/**
|
||||
* Conflict-free Replicated Data Types (CRDT)s. Unless otherwise noted, the implementations of these interfaces are not
|
||||
* thread-safe since they are expected to be used within a thread-safe process, and sent across a network to another thread-safe
|
||||
* process where they will be merged together.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
public final class CRDT {
|
||||
|
||||
/**
|
||||
* Create a new CRDT counter. The operations on this counter are not threadsafe.
|
||||
*
|
||||
* @return the new counter; never null
|
||||
*/
|
||||
public static GCounter newGCounter() {
|
||||
return new StateBasedGCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CRDT counter pre-populated with the given value. The operations on this counter are not threadsafe.
|
||||
*
|
||||
* @param adds the number of adds
|
||||
* @return the new counter; never null
|
||||
*/
|
||||
public static GCounter newGCounter(long adds) {
|
||||
return new StateBasedGCounter(adds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CRDT counter. The operations on this counter are not threadsafe.
|
||||
*
|
||||
* @return the new counter; never null
|
||||
*/
|
||||
public static PNCounter newPNCounter() {
|
||||
return new StateBasedPNCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CRDT counter pre-populated with the given values. The operations on this counter are not threadsafe.
|
||||
*
|
||||
* @param adds the number of adds
|
||||
* @param removes the number of removes
|
||||
* @return the new counter; never null
|
||||
*/
|
||||
public static PNCounter newPNCounter(long adds, long removes) {
|
||||
return new StateBasedPNCounter(adds, removes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CRDT counter that records how much the value has changed since last reset. The operations on this counter are
|
||||
* not threadsafe.
|
||||
*
|
||||
* @return the new counter; never null
|
||||
*/
|
||||
public static DeltaCounter newDeltaCounter() {
|
||||
return new StateBasedPNDeltaCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CRDT counter that records how much the value has changed since last reset. The operations on this counter are
|
||||
* not threadsafe.
|
||||
*
|
||||
* @param totalAdds the total number of adds
|
||||
* @param totalRemoves the total number of removes
|
||||
* @param recentAdds the recent number of adds since last {@link DeltaCounter#reset()}
|
||||
* @param recentRemoves the recent number of removes since last {@link DeltaCounter#reset()}
|
||||
* @return the new counter; never null
|
||||
*/
|
||||
public static DeltaCounter newDeltaCounter(long totalAdds, long totalRemoves, long recentAdds, long recentRemoves) {
|
||||
return new StateBasedPNDeltaCounter(totalAdds, totalRemoves, recentAdds, recentRemoves);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CRDT counter that records how much the value has changed since last reset. The operations on this counter are
|
||||
* not threadsafe.
|
||||
*
|
||||
* @param count the {@link DeltaCount} instance that should be used to pre-populate the new counter; may be null
|
||||
* @return the new counter; never null
|
||||
*/
|
||||
public static DeltaCounter newDeltaCounter(DeltaCount count) {
|
||||
if (count == null) return new StateBasedPNDeltaCounter();
|
||||
return new StateBasedPNDeltaCounter(count.getIncrement(), count.getDecrement(),
|
||||
count.getChanges().getIncrement(), count.getChanges().getDecrement());
|
||||
}
|
||||
|
||||
private CRDT() {
|
||||
}
|
||||
}
|
18
debezium-core/src/main/java/io/debezium/crdt/Count.java
Normal file
18
debezium-core/src/main/java/io/debezium/crdt/Count.java
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
/**
|
||||
* A read-only result of a counter. The value may or may not change depending upon how the value was obtained.
|
||||
*/
|
||||
public interface Count {
|
||||
/**
|
||||
* Get the current value.
|
||||
*
|
||||
* @return the current value
|
||||
*/
|
||||
long get();
|
||||
}
|
34
debezium-core/src/main/java/io/debezium/crdt/DeltaCount.java
Normal file
34
debezium-core/src/main/java/io/debezium/crdt/DeltaCount.java
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
/**
|
||||
* A {@link Count} that also tracks changes to the value within the last interval.
|
||||
*/
|
||||
public interface DeltaCount extends PNCount {
|
||||
|
||||
/**
|
||||
* Get the changes in the current value during the last interval.
|
||||
*
|
||||
* @return the changes in the value during the last interval; never null
|
||||
*/
|
||||
PNCount getChanges();
|
||||
|
||||
/**
|
||||
* Determine if there are any changes in this count.
|
||||
* @return {@code true} if there are non-zero {@link #getChanges() changes}, or {@code false} otherwise
|
||||
*/
|
||||
default boolean hasChanges() {
|
||||
PNCount changes = getChanges();
|
||||
return changes.getIncrement() != 0 || changes.getDecrement() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of this count prior to the {@link #getChanges() changes}.
|
||||
* @return the prior count; never null
|
||||
*/
|
||||
Count getPriorCount();
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
import io.debezium.annotation.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A simple counter that maintains a single changing value by separately tracking the positive and negative changes, and by
|
||||
* tracking recent {@link #getChanges() changes} in this value since last {@link #reset() reset}. It is
|
||||
* inspired by the conflict-free replicated data type (CRDT) P-N Counter. The semantics ensure the value converges toward the
|
||||
* global number of increments minus the number of decrements. The global total can be calculated by {@link #merge(Count) merging}
|
||||
* all the replicated instances, without regard to order of merging.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public interface DeltaCounter extends PNCounter, DeltaCount {
|
||||
/**
|
||||
* Increment the counter and get the result.
|
||||
*
|
||||
* @return this instance so methods can be chained together; never null
|
||||
*/
|
||||
@Override
|
||||
DeltaCounter increment();
|
||||
|
||||
/**
|
||||
* Decrement the counter and get the result.
|
||||
*
|
||||
* @return this instance so methods can be chained together; never null
|
||||
*/
|
||||
@Override
|
||||
DeltaCounter decrement();
|
||||
|
||||
/**
|
||||
* Merge the supplied counter into this counter.
|
||||
*
|
||||
* @param other the other counter to merge into this one; may be null
|
||||
* @return this counter so that methods can be chained
|
||||
*/
|
||||
@Override
|
||||
DeltaCounter merge(Count other);
|
||||
|
||||
/**
|
||||
* Start a new interval and reset the {@link #getChanges()} to initial values.
|
||||
*/
|
||||
void reset();
|
||||
}
|
20
debezium-core/src/main/java/io/debezium/crdt/GCount.java
Normal file
20
debezium-core/src/main/java/io/debezium/crdt/GCount.java
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
/**
|
||||
* A read-only result of the state of a grow-only {@link GCounter}. The value may or may not change depending upon how the value
|
||||
* was obtained.
|
||||
*/
|
||||
public interface GCount extends Count {
|
||||
|
||||
/**
|
||||
* Get the amount that the value incremented.
|
||||
*
|
||||
* @return the incremented value
|
||||
*/
|
||||
long getIncrement();
|
||||
}
|
44
debezium-core/src/main/java/io/debezium/crdt/GCounter.java
Normal file
44
debezium-core/src/main/java/io/debezium/crdt/GCounter.java
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
import io.debezium.annotation.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A simple grow-only counter that maintains a single changing value by tracking the positive changes to the value. It is
|
||||
* inspired by the conflict-free replicated data type (CRDT) G Counter.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public interface GCounter extends GCount {
|
||||
/**
|
||||
* Increment the counter and get the result.
|
||||
*
|
||||
* @return this instance so methods can be chained together; never null
|
||||
*/
|
||||
GCounter increment();
|
||||
|
||||
/**
|
||||
* Increment the counter and get the result.
|
||||
*
|
||||
* @return the current result after incrementing
|
||||
*/
|
||||
long incrementAndGet();
|
||||
|
||||
/**
|
||||
* Increment the counter and get the result.
|
||||
*
|
||||
* @return the current result before incrementing
|
||||
*/
|
||||
long getAndIncrement();
|
||||
|
||||
/**
|
||||
* Merge the supplied counter into this counter.
|
||||
*
|
||||
* @param other the other counter to merge into this one; may be null
|
||||
* @return this counter so that methods can be chained
|
||||
*/
|
||||
GCounter merge(Count other);
|
||||
}
|
30
debezium-core/src/main/java/io/debezium/crdt/PNCount.java
Normal file
30
debezium-core/src/main/java/io/debezium/crdt/PNCount.java
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
/**
|
||||
* A read-only result of the state of a {@link PNCounter}. The value may or may not change depending upon how the value was
|
||||
* obtained.
|
||||
*/
|
||||
public interface PNCount extends GCount {
|
||||
/**
|
||||
* Get the current value.
|
||||
*
|
||||
* @return the current value
|
||||
*/
|
||||
@Override
|
||||
default long get() {
|
||||
return getIncrement() - getDecrement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount that the value decremented. The {@link #get() value} is the {@link #getIncrement() total
|
||||
* increments} minus the {@link #getDecrement() total decrements}
|
||||
*
|
||||
* @return the decremented value
|
||||
*/
|
||||
long getDecrement();
|
||||
}
|
56
debezium-core/src/main/java/io/debezium/crdt/PNCounter.java
Normal file
56
debezium-core/src/main/java/io/debezium/crdt/PNCounter.java
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
import io.debezium.annotation.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A simple counter that maintains a single changing value by separately tracking the positive and negative changes. It is
|
||||
* inspired by the conflict-free replicated data type (CRDT) P-N Counter. The semantics ensure the value converges toward the
|
||||
* global number of increments minus the number of decrements. The global total can be calculated by {@link #merge(Count) merging}
|
||||
* all the replicated instances, without regard to order of merging.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public interface PNCounter extends PNCount, GCounter {
|
||||
|
||||
/**
|
||||
* Increment the counter and get the result.
|
||||
*
|
||||
* @return this instance so methods can be chained together; never null
|
||||
*/
|
||||
@Override
|
||||
PNCounter increment();
|
||||
|
||||
/**
|
||||
* Decrement the counter and get the result.
|
||||
*
|
||||
* @return this instance so methods can be chained together; never null
|
||||
*/
|
||||
PNCounter decrement();
|
||||
|
||||
/**
|
||||
* Decrement the counter and get the result.
|
||||
*
|
||||
* @return the current result after decrementing
|
||||
*/
|
||||
long decrementAndGet();
|
||||
|
||||
/**
|
||||
* Decrement the counter and get the result.
|
||||
*
|
||||
* @return the current result before decrementing
|
||||
*/
|
||||
long getAndDecrement();
|
||||
|
||||
/**
|
||||
* Merge the supplied counter into this counter.
|
||||
*
|
||||
* @param other the other counter to merge into this one; may be null
|
||||
* @return this counter so that methods can be chained
|
||||
*/
|
||||
@Override
|
||||
PNCounter merge(Count other);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
import io.debezium.annotation.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
class StateBasedGCounter implements GCounter {
|
||||
private long adds;
|
||||
|
||||
protected StateBasedGCounter() {
|
||||
this(0L);
|
||||
}
|
||||
|
||||
protected StateBasedGCounter(long adds) {
|
||||
this.adds = adds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GCounter increment() {
|
||||
++adds;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long incrementAndGet() {
|
||||
return ++adds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAndIncrement() {
|
||||
return adds++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long get() {
|
||||
return adds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIncrement() {
|
||||
return adds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GCounter merge(Count other) {
|
||||
if (other instanceof GCount) {
|
||||
GCount changes = (GCount)other;
|
||||
this.adds += changes.getIncrement();
|
||||
} else if (other instanceof Count) {
|
||||
Count changes = other;
|
||||
this.adds += changes.get();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "+" + adds;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
import io.debezium.annotation.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
class StateBasedPNCounter implements PNCounter {
|
||||
private long adds;
|
||||
private long removes;
|
||||
|
||||
protected StateBasedPNCounter() {
|
||||
this(0L, 0L);
|
||||
}
|
||||
|
||||
protected StateBasedPNCounter(long adds, long removes) {
|
||||
this.adds = adds;
|
||||
this.removes = removes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PNCounter increment() {
|
||||
++adds;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PNCounter decrement() {
|
||||
++removes;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long incrementAndGet() {
|
||||
return ++adds - removes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long decrementAndGet() {
|
||||
return adds - ++removes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAndIncrement() {
|
||||
return adds++ - removes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAndDecrement() {
|
||||
return adds - removes++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long get() {
|
||||
return adds - removes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIncrement() {
|
||||
return adds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDecrement() {
|
||||
return removes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PNCounter merge(Count other) {
|
||||
if (other instanceof PNCount) {
|
||||
PNCount changes = (PNCount)other;
|
||||
this.adds += changes.getIncrement();
|
||||
this.removes += changes.getDecrement();
|
||||
} else if (other instanceof GCount) {
|
||||
GCount changes = (GCount)other;
|
||||
this.adds += changes.getIncrement();
|
||||
} else if (other instanceof Count) {
|
||||
Count changes = other;
|
||||
this.adds += changes.get();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "+" + adds + " -" + removes;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.crdt;
|
||||
|
||||
import io.debezium.annotation.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe class StateBasedPNDeltaCounter extends StateBasedPNCounter implements DeltaCounter {
|
||||
private PNCounter delta;
|
||||
|
||||
protected StateBasedPNDeltaCounter() {
|
||||
this(0L, 0L, 0L, 0L);
|
||||
}
|
||||
|
||||
protected StateBasedPNDeltaCounter(long totalAdds, long totalRemoves, long recentAdds, long recentRemoves) {
|
||||
super(totalAdds, totalRemoves);
|
||||
delta = new StateBasedPNCounter(recentAdds, recentRemoves);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeltaCounter increment() {
|
||||
super.increment();
|
||||
delta.increment();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeltaCounter decrement() {
|
||||
super.decrement();
|
||||
delta.decrement();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long incrementAndGet() {
|
||||
delta.incrementAndGet();
|
||||
return super.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long decrementAndGet() {
|
||||
delta.decrementAndGet();
|
||||
return super.decrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAndIncrement() {
|
||||
delta.getAndIncrement();
|
||||
return super.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAndDecrement() {
|
||||
delta.getAndDecrement();
|
||||
return super.getAndDecrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PNCount getChanges() {
|
||||
return delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChanges() {
|
||||
return delta.getIncrement() != 0 || delta.getDecrement() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Count getPriorCount() {
|
||||
long value = super.get() - delta.get();
|
||||
return new Count() {
|
||||
@Override
|
||||
public long get() {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
delta = new StateBasedPNCounter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeltaCounter merge(Count other) {
|
||||
if (other instanceof DeltaCount) {
|
||||
// Just merge in the *changes* ...
|
||||
DeltaCount that = (DeltaCount) other;
|
||||
this.delta.merge(that.getChanges());
|
||||
super.merge(that.getChanges());
|
||||
} else {
|
||||
super.merge(other);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " (changes " + this.delta + ")";
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.function;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Callable {
|
||||
void call();
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.function;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author Randall Hauch
|
||||
*
|
||||
*/
|
||||
public class Predicates {
|
||||
|
||||
public static <T> Predicate<T> notNull() {
|
||||
return new Predicate<T>() {
|
||||
@Override
|
||||
public boolean test(T t) {
|
||||
return t != null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Predicates() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.jdbc;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
import io.debezium.config.Configuration;
|
||||
import io.debezium.util.Collect;
|
||||
|
||||
/**
|
||||
* A specialized configuration for the Debezium driver.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@Immutable
|
||||
public interface JdbcConfiguration extends Configuration {
|
||||
|
||||
public static interface Builder extends Configuration.ConfigBuilder<JdbcConfiguration, Builder> {
|
||||
default Builder withUser(String username) {
|
||||
return with(Field.USER, username);
|
||||
}
|
||||
|
||||
default Builder withPassword(String password) {
|
||||
return with(Field.PASSWORD, password);
|
||||
}
|
||||
|
||||
default Builder withHostname(String hostname) {
|
||||
return with(Field.HOSTNAME, hostname);
|
||||
}
|
||||
|
||||
default Builder withDatabase(String databaseName) {
|
||||
return with(Field.DATABASE, databaseName);
|
||||
}
|
||||
|
||||
default Builder withPort(int port) {
|
||||
return with(Field.PORT, port);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder configuration builder} that starts with a copy of the supplied configuration.
|
||||
*
|
||||
* @param config the configuration to copy
|
||||
* @return the configuration builder
|
||||
*/
|
||||
public static Builder copy(Configuration config) {
|
||||
return new Builder() {
|
||||
private Properties props = config.asProperties();
|
||||
|
||||
@Override
|
||||
public Builder with(String key, String value) {
|
||||
props.setProperty(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcConfiguration build() {
|
||||
return JdbcConfiguration.adapt(Configuration.from(props));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return props.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Builder configuration builder} that starts with an empty configuration.
|
||||
*
|
||||
* @return the configuration builder
|
||||
*/
|
||||
public static Builder create() {
|
||||
return new Builder() {
|
||||
private Properties props = new Properties();
|
||||
|
||||
@Override
|
||||
public Builder with(String key, String value) {
|
||||
props.setProperty(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcConfiguration build() {
|
||||
return JdbcConfiguration.adapt(Configuration.from(props));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return props.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The pre-defined fields for JDBC configurations.
|
||||
*/
|
||||
public static interface Field {
|
||||
public static final String USER = "user";
|
||||
public static final String PASSWORD = "password";
|
||||
public static final String DATABASE = "dbname";
|
||||
public static final String HOSTNAME = "hostname";
|
||||
public static final String PORT = "port";
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of pre-defined fields for JDBC configurations.
|
||||
*/
|
||||
public static Set<String> ALL_KNOWN_FIELDS = Collect.unmodifiableSet(Field.DATABASE, Field.USER, Field.PASSWORD, Field.HOSTNAME,
|
||||
Field.PORT);
|
||||
|
||||
/**
|
||||
* Get a predicate that determines if supplied keys are {@link Field pre-defined field names}.
|
||||
*
|
||||
* @return the predicate; never null
|
||||
*/
|
||||
default Predicate<String> knownFieldNames() {
|
||||
return ALL_KNOWN_FIELDS::contains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a view of this configuration that does not contain the {@link #knownFieldNames() known fields}.
|
||||
*
|
||||
* @return the filtered view of this configuration; never null
|
||||
*/
|
||||
default Configuration withoutKnownFields() {
|
||||
return filter(knownFieldNames().negate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link JdbcConfiguration} adapter for the given {@link Configuration}.
|
||||
*
|
||||
* @param config the configuration; may not be null
|
||||
* @return the ClientConfiguration; never null
|
||||
*/
|
||||
public static JdbcConfiguration adapt(Configuration config) {
|
||||
if (config instanceof JdbcConfiguration) return (JdbcConfiguration) config;
|
||||
return new JdbcConfiguration() {
|
||||
@Override
|
||||
public Set<String> keys() {
|
||||
return config.keys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key) {
|
||||
return config.getString(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return config.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hostname property from the configuration.
|
||||
*
|
||||
* @return the host name, or null if there is none.
|
||||
*/
|
||||
default String getHostname() {
|
||||
return getHostname(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hostname property from the configuration.
|
||||
*
|
||||
* @param defaultValue the value to use by default
|
||||
* @return the host name, or the {@code defaultValue} if there is no such property
|
||||
*/
|
||||
default String getHostname(String defaultValue) {
|
||||
return getString(Field.HOSTNAME, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port property from the configuration.
|
||||
*
|
||||
* @return the port number, or null if there is none.
|
||||
*/
|
||||
default String getPortAsString() {
|
||||
return getPort(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port property from the configuration.
|
||||
*
|
||||
* @param defaultValue the value to use by default
|
||||
* @return the port, or the {@code defaultValue} if there is no such property
|
||||
*/
|
||||
default String getPort(String defaultValue) {
|
||||
return getString(Field.PORT, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port property from the configuration.
|
||||
*
|
||||
* @return the port number, or null if there is none.
|
||||
*/
|
||||
default Integer getPort() {
|
||||
return getInteger(Field.PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port property from the configuration.
|
||||
*
|
||||
* @param defaultValue the value to use by default
|
||||
* @return the port, or the {@code defaultValue} if there is no such property
|
||||
*/
|
||||
default int getPort(int defaultValue) {
|
||||
return getInteger(Field.PORT, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database name property from the configuration.
|
||||
*
|
||||
* @return the database name, or null if there is none.
|
||||
*/
|
||||
default String getDatabase() {
|
||||
return getDatabase(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database name property from the configuration.
|
||||
*
|
||||
* @param defaultValue the value to use by default
|
||||
* @return the database name, or the {@code defaultValue} if there is no such property
|
||||
*/
|
||||
default String getDatabase(String defaultValue) {
|
||||
return getString(Field.DATABASE, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user property from the configuration.
|
||||
*
|
||||
* @return the username, or null if there is none.
|
||||
*/
|
||||
default String getUser() {
|
||||
return getUser(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user property from the configuration.
|
||||
*
|
||||
* @param defaultValue the value to use by default
|
||||
* @return the username, or the {@code defaultValue} if there is no such property
|
||||
*/
|
||||
default String getUser(String defaultValue) {
|
||||
return getString(Field.USER, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the password property from the configuration.
|
||||
*
|
||||
* @return the password value, or null if there is none.
|
||||
*/
|
||||
default String getPassword() {
|
||||
return getPassword(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the password property from the configuration.
|
||||
*
|
||||
* @param defaultValue the value to use by default
|
||||
* @return the password, or the {@code defaultValue} if there is no such property
|
||||
*/
|
||||
default String getPassword(String defaultValue) {
|
||||
return getString(Field.PASSWORD, defaultValue);
|
||||
}
|
||||
}
|
286
debezium-core/src/main/java/io/debezium/jdbc/JdbcConnection.java
Normal file
286
debezium-core/src/main/java/io/debezium/jdbc/JdbcConnection.java
Normal file
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.debezium.annotation.ThreadSafe;
|
||||
import io.debezium.config.Configuration;
|
||||
import io.debezium.util.Strings;
|
||||
|
||||
/**
|
||||
* A utility that simplifies using a JDBC connection and executing transactions composed of multiple statements.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
public class JdbcConnection implements AutoCloseable {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(JdbcConnection.class);
|
||||
|
||||
/**
|
||||
* Establishes JDBC connections.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@ThreadSafe
|
||||
public static interface ConnectionFactory {
|
||||
/**
|
||||
* Establish a connection to the database denoted by the given configuration.
|
||||
*
|
||||
* @param config the configuration with JDBC connection information
|
||||
* @return the JDBC connection; may not be null
|
||||
* @throws SQLException if there is an error connecting to the database
|
||||
*/
|
||||
Connection connect(JdbcConfiguration config) throws SQLException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines multiple JDBC operations.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public static interface Operations {
|
||||
/**
|
||||
* Apply a series of operations against the given JDBC statement.
|
||||
*
|
||||
* @param statement the JDBC statement to use to execute one or more operations
|
||||
* @throws SQLException if there is an error connecting to the database or executing the statements
|
||||
*/
|
||||
void apply(Statement statement) throws SQLException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ConnectionFactory} that replaces variables in the supplied URL pattern. Variables include:
|
||||
* <ul>
|
||||
* <li><code>${hostname}</code></li>
|
||||
* <li><code>${port}</code></li>
|
||||
* <li><code>${dbname}</code></li>
|
||||
* <li><code>${username}</code></li>
|
||||
* <li><code>${password}</code></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param urlPattern the URL pattern string; may not be null
|
||||
* @return the connection factory
|
||||
*/
|
||||
protected static ConnectionFactory patternBasedFactory(String urlPattern) {
|
||||
return (config) -> {
|
||||
LOGGER.trace("Config: {}", config.asProperties());
|
||||
Properties props = config.asProperties();
|
||||
String url = findAndReplace(urlPattern, props,
|
||||
JdbcConfiguration.Field.HOSTNAME,
|
||||
JdbcConfiguration.Field.PORT,
|
||||
JdbcConfiguration.Field.USER,
|
||||
JdbcConfiguration.Field.PASSWORD,
|
||||
JdbcConfiguration.Field.DATABASE);
|
||||
LOGGER.trace("Props: {}", props);
|
||||
LOGGER.trace("URL: {}", url);
|
||||
Connection conn = DriverManager.getConnection(url, props);
|
||||
LOGGER.debug("Connected to {} with {}", url, props);
|
||||
return conn;
|
||||
};
|
||||
}
|
||||
|
||||
private static String findAndReplace(String url, Properties props, String... variableNames) {
|
||||
for (String variable : variableNames) {
|
||||
if (url.contains("${" + variable + "}")) {
|
||||
// Otherwise, we have to remove it from the properties ...
|
||||
String value = props.getProperty(variable);
|
||||
props.remove(variable);
|
||||
// And replace the variable ...
|
||||
url = url.replaceAll("\\$\\{" + variable + "\\}", value);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private final Configuration config;
|
||||
private final ConnectionFactory factory;
|
||||
private final Operations initialOps;
|
||||
private volatile Connection conn;
|
||||
|
||||
/**
|
||||
* Create a new instance with the given configuration and connection factory.
|
||||
*
|
||||
* @param config the configuration; may not be null
|
||||
* @param connectionFactory the connection factory; may not be null
|
||||
*/
|
||||
public JdbcConnection(Configuration config, ConnectionFactory connectionFactory) {
|
||||
this(config, connectionFactory, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the given configuration and connection factory, and specify the operations that should be
|
||||
* run against each newly-established connection.
|
||||
*
|
||||
* @param config the configuration; may not be null
|
||||
* @param connectionFactory the connection factory; may not be null
|
||||
* @param initialOperations the initial operations that should be run on each new connection; may be null
|
||||
*/
|
||||
public JdbcConnection(Configuration config, ConnectionFactory connectionFactory, Operations initialOperations) {
|
||||
this.config = config;
|
||||
this.factory = connectionFactory;
|
||||
this.initialOps = initialOperations;
|
||||
this.conn = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a connection to the database is established.
|
||||
*
|
||||
* @return this object for chaining methods together
|
||||
* @throws SQLException if there is an error connecting to the database
|
||||
*/
|
||||
public JdbcConnection connect() throws SQLException {
|
||||
connection();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a series of SQL statements as a single transaction.
|
||||
*
|
||||
* @param sqlStatements the SQL statements that are to be performed as a single transaction
|
||||
* @return this object for chaining methods together
|
||||
* @throws SQLException if there is an error connecting to the database or executing the statements
|
||||
* @see #execute(Operations)
|
||||
*/
|
||||
public JdbcConnection execute(String... sqlStatements) throws SQLException {
|
||||
return execute(statement -> {
|
||||
for (String sqlStatement : sqlStatements) {
|
||||
if (sqlStatement != null) statement.execute(sqlStatement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a series of operations as a single transaction.
|
||||
*
|
||||
* @param operations the function that will be called with a newly-created {@link Statement}, and that performs
|
||||
* one or more operations on that statement object
|
||||
* @return this object for chaining methods together
|
||||
* @throws SQLException if there is an error connecting to the database or executing the statements
|
||||
*/
|
||||
public JdbcConnection execute(Operations operations) throws SQLException {
|
||||
Connection conn = connection();
|
||||
conn.setAutoCommit(false);
|
||||
try (Statement statement = conn.createStatement();) {
|
||||
operations.apply(statement);
|
||||
conn.commit();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a SQL query.
|
||||
*
|
||||
* @param query the SQL query
|
||||
* @param resultConsumer the consumer of the query results
|
||||
* @return this object for chaining methods together
|
||||
* @throws SQLException if there is an error connecting to the database or executing the statements
|
||||
* @see #execute(Operations)
|
||||
*/
|
||||
public JdbcConnection query(String query, Consumer<ResultSet> resultConsumer) throws SQLException {
|
||||
Connection conn = connection();
|
||||
conn.setAutoCommit(false);
|
||||
try (Statement statement = conn.createStatement();) {
|
||||
ResultSet resultSet = statement.executeQuery(query);
|
||||
if (resultConsumer != null) resultConsumer.accept(resultSet);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void print(ResultSet resultSet ) {
|
||||
// CHECKSTYLE:OFF
|
||||
print(resultSet,System.out::println);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
|
||||
public void print(ResultSet resultSet, Consumer<String> lines ) {
|
||||
try {
|
||||
ResultSetMetaData rsmd = resultSet.getMetaData();
|
||||
int columnCount = rsmd.getColumnCount();
|
||||
int[] columnSizes = findMaxLength(resultSet);
|
||||
lines.accept(delimiter(columnCount, columnSizes));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for ( int i=1; i<=columnCount; i++ ) {
|
||||
if (i > 1) sb.append(" | ");
|
||||
sb.append(Strings.setLength(rsmd.getColumnLabel(i),columnSizes[i],' '));
|
||||
}
|
||||
lines.accept(sb.toString());
|
||||
sb.setLength(0);
|
||||
lines.accept(delimiter(columnCount, columnSizes));
|
||||
while (resultSet.next()) {
|
||||
sb.setLength(0);
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
if (i > 1) sb.append(" | ");
|
||||
sb.append(Strings.setLength(resultSet.getString(i),columnSizes[i],' '));
|
||||
}
|
||||
lines.accept(sb.toString());
|
||||
sb.setLength(0);
|
||||
}
|
||||
lines.accept(delimiter(columnCount, columnSizes));
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String delimiter( int columnCount, int[] columnSizes ) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for ( int i=1; i<=columnCount; i++ ) {
|
||||
if (i > 1) sb.append("---");
|
||||
sb.append(Strings.createString('-',columnSizes[i]));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int[] findMaxLength( ResultSet resultSet ) throws SQLException {
|
||||
ResultSetMetaData rsmd = resultSet.getMetaData();
|
||||
int columnCount = rsmd.getColumnCount();
|
||||
int[] columnSizes = new int[columnCount+1];
|
||||
for ( int i=1; i<=columnCount; i++ ) {
|
||||
columnSizes[i] = Math.max(columnSizes[i], rsmd.getColumnLabel(i).length());
|
||||
}
|
||||
while (resultSet.next()) {
|
||||
for (int i = 1; i <= columnCount; i++) {
|
||||
String value = resultSet.getString(i);
|
||||
if ( value != null ) columnSizes[i] = Math.max(columnSizes[i], value.length());
|
||||
}
|
||||
}
|
||||
resultSet.beforeFirst();
|
||||
return columnSizes;
|
||||
}
|
||||
|
||||
protected synchronized Connection connection() throws SQLException {
|
||||
if (conn == null) {
|
||||
conn = factory.connect(JdbcConfiguration.adapt(config));
|
||||
if (conn == null) throw new SQLException("Unable to obtain a JDBC connection");
|
||||
// Always run the initial operations on this new connection
|
||||
if (initialOps != null) execute(initialOps);
|
||||
}
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection and release any resources.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() throws SQLException {
|
||||
if (conn != null) {
|
||||
try {
|
||||
conn.close();
|
||||
} finally {
|
||||
conn = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
debezium-core/src/main/java/io/debezium/util/Clock.java
Normal file
33
debezium-core/src/main/java/io/debezium/util/Clock.java
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
/**
|
||||
* @author Randall Hauch
|
||||
*
|
||||
*/
|
||||
public interface Clock {
|
||||
|
||||
public static final Clock SYSTEM = new Clock() {
|
||||
@Override
|
||||
public long currentTimeInMillis() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
@Override
|
||||
public long currentTimeInNanos() {
|
||||
return System.nanoTime();
|
||||
}
|
||||
};
|
||||
|
||||
public static Clock system() {
|
||||
return SYSTEM;
|
||||
}
|
||||
|
||||
public long currentTimeInNanos();
|
||||
|
||||
public long currentTimeInMillis();
|
||||
|
||||
}
|
116
debezium-core/src/main/java/io/debezium/util/Collect.java
Normal file
116
debezium-core/src/main/java/io/debezium/util/Collect.java
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A set of utilities for more easily creating various kinds of collections.
|
||||
*/
|
||||
public class Collect {
|
||||
|
||||
/**
|
||||
* Create a fixed sized Map that removes the least-recently used entry when the map becomes too large. The supplied
|
||||
* {@code maximumNumberOfEntries} should be a power of 2 to efficiently make efficient use of memory. If not, the resulting
|
||||
* map will be able to contain no more than {@code maximumNumberOfEntries} entries, but the underlying map will have a
|
||||
* capacity that is the next power of larger than the supplied {@code maximumNumberOfEntries} value so that it can hold
|
||||
* the required number of entries.
|
||||
*
|
||||
* @param maximumNumberOfEntries the maximum number of entries allowed in the map; should be a power of 2
|
||||
* @return the map that is limited in size by the specified number of entries; never null
|
||||
*/
|
||||
public static <K, V> Map<K, V> fixedSizeMap(int maximumNumberOfEntries) {
|
||||
return new LinkedHashMap<K, V>(maximumNumberOfEntries + 1, .75F, true) { // throws illegal argument if < 0
|
||||
private static final long serialVersionUID = 1L;
|
||||
final int evictionSize = maximumNumberOfEntries - 1;
|
||||
|
||||
@Override
|
||||
public boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||
return size() > evictionSize;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Set<T> unmodifiableSet(@SuppressWarnings("unchecked") T... values) {
|
||||
return unmodifiableSet(arrayListOf(values));
|
||||
}
|
||||
|
||||
public static <T> Set<T> unmodifiableSet(Collection<T> values) {
|
||||
return Collections.unmodifiableSet(new HashSet<T>(values));
|
||||
}
|
||||
|
||||
public static <T> Set<T> unmodifiableSet(Set<T> values) {
|
||||
return Collections.unmodifiableSet(values);
|
||||
}
|
||||
|
||||
public static <T> List<T> arrayListOf(T[] values) {
|
||||
List<T> result = new ArrayList<>();
|
||||
for (T value : values) {
|
||||
if (value != null) result.add(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> arrayListOf(T first, @SuppressWarnings("unchecked") T... additional) {
|
||||
List<T> result = new ArrayList<>();
|
||||
result.add(first);
|
||||
for (T another : additional) {
|
||||
if (another != null) result.add(another);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> arrayListOf(Iterable<T> values) {
|
||||
List<T> result = new ArrayList<>();
|
||||
values.forEach((value) -> result.add(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(K key, V value) {
|
||||
return Collections.singletonMap(key, value);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> hashMapOf(K key, V value) {
|
||||
Map<K, V> map = new HashMap<>();
|
||||
map.put(key, value);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> hashMapOf(K key1, V value1, K key2, V value2) {
|
||||
Map<K, V> map = new HashMap<>();
|
||||
map.put(key1, value1);
|
||||
map.put(key2, value2);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> hashMapOf(K key1, V value1, K key2, V value2, K key3, V value3) {
|
||||
Map<K, V> map = new HashMap<>();
|
||||
map.put(key1, value1);
|
||||
map.put(key2, value2);
|
||||
map.put(key3, value3);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> hashMapOf(K key1, V value1, K key2, V value2, K key3, V value3, K key4, V value4) {
|
||||
Map<K, V> map = new HashMap<>();
|
||||
map.put(key1, value1);
|
||||
map.put(key2, value2);
|
||||
map.put(key3, value3);
|
||||
map.put(key4, value4);
|
||||
return map;
|
||||
}
|
||||
|
||||
private Collect() {
|
||||
}
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* Utility for parsing and accessing the command line options and parameters.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@Immutable
|
||||
public class CommandLineOptions {
|
||||
|
||||
/**
|
||||
* Parse the array of arguments passed to a Java {@code main} method and create a {@link CommandLineOptions} instance.
|
||||
*
|
||||
* @param args the {@code main} method's parameters; may not be null
|
||||
* @return the representation of the command line options and parameters; never null
|
||||
*/
|
||||
public static CommandLineOptions parse(String[] args) {
|
||||
Map<String, String> options = new HashMap<>();
|
||||
List<String> params = new ArrayList<>();
|
||||
List<String> optionNames = new ArrayList<>();
|
||||
String optionName = null;
|
||||
for (String value : args) {
|
||||
value = value.trim();
|
||||
if (value.startsWith("-")) {
|
||||
optionName = value;
|
||||
options.put(optionName, "true");
|
||||
optionNames.add(optionName);
|
||||
} else {
|
||||
if (optionName != null)
|
||||
options.put(optionName, value);
|
||||
else
|
||||
params.add(value);
|
||||
}
|
||||
}
|
||||
return new CommandLineOptions(options, params, optionNames);
|
||||
}
|
||||
|
||||
private final Map<String, String> options;
|
||||
private final List<String> params;
|
||||
private final List<String> orderedOptionNames;
|
||||
|
||||
private CommandLineOptions(Map<String, String> options, List<String> params, List<String> orderedOptionNames) {
|
||||
this.options = options;
|
||||
this.params = params;
|
||||
this.orderedOptionNames = orderedOptionNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the option with the given name was used on the command line.
|
||||
* <p>
|
||||
* The supplied option name is trimmed before matching against the command line arguments. Note that any special characters
|
||||
* such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @return true if the exact option was used, or false otherwise or if the supplied option name is null
|
||||
* @see #hasOption(String, String)
|
||||
*/
|
||||
public boolean hasOption(String name) {
|
||||
return hasOption(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the option with one of the given names was used on the command line.
|
||||
* <p>
|
||||
* The supplied option name and alternative names are trimmed before matching against the command line arguments. Note that
|
||||
* any special characters such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param alternativeName an alternative name for the option; may be null
|
||||
* @return true if the exact option was used, or false otherwise or if both the supplied option name and alternative name are
|
||||
* null
|
||||
* @see #hasOption(String)
|
||||
*/
|
||||
public boolean hasOption(String name, String alternativeName) {
|
||||
return getOption(name, alternativeName, null) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the value associated with the named option.
|
||||
* <p>
|
||||
* The supplied option name is trimmed before matching against the command line arguments. Note that any special characters
|
||||
* such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @return the value associated with the option, or null if no option was found or the name is null
|
||||
* @see #getOption(String, String)
|
||||
* @see #getOption(String, String, String)
|
||||
*/
|
||||
public String getOption(String name) {
|
||||
return getOption(name, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the value associated with the named option, using the supplied default value if none is found.
|
||||
* <p>
|
||||
* The supplied option name is trimmed before matching against the command line arguments. Note that any special characters
|
||||
* such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or the name is null
|
||||
* @see #getOption(String)
|
||||
* @see #getOption(String, String, String)
|
||||
*/
|
||||
public String getOption(String name, String defaultValue) {
|
||||
return getOption(name, null, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the value associated with the option given the name and alternative name of the option, using the supplied default
|
||||
* value if none is found.
|
||||
* <p>
|
||||
* The supplied option name and alternative names are trimmed before matching against the command line arguments. Note that
|
||||
* any special characters such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param alternativeName an alternative name for the option; may be null
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or if both the name and alternative
|
||||
* name are null
|
||||
* @see #getOption(String)
|
||||
* @see #getOption(String, String)
|
||||
*/
|
||||
public String getOption(String name, String alternativeName, String defaultValue) {
|
||||
recordOptionUsed(name, alternativeName);
|
||||
String result = options.get(name.trim());
|
||||
if (result == null && alternativeName != null) result = options.get(alternativeName.trim());
|
||||
return result != null ? result : defaultValue;
|
||||
}
|
||||
|
||||
protected void recordOptionUsed(String name, String alternativeName) {
|
||||
orderedOptionNames.remove(name.trim());
|
||||
if (alternativeName != null) orderedOptionNames.remove(alternativeName.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the long value associated with the named option, using the supplied default value if none is found or if the value
|
||||
* cannot be parsed as a long value.
|
||||
* <p>
|
||||
* The supplied option name is trimmed before matching against the command line arguments. Note that any special characters
|
||||
* such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or the name is null
|
||||
* @see #getOption(String, String, long)
|
||||
*/
|
||||
public long getOption(String name, long defaultValue) {
|
||||
return getOption(name, null, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the long value associated with the option given the name and alternative name of the option, using the supplied
|
||||
* default value if none is found or if the value cannot be parsed as a long value.
|
||||
* <p>
|
||||
* The supplied option name and alternative names are trimmed before matching against the command line arguments. Note that
|
||||
* any special characters such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param alternativeName an alternative name for the option; may be null
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or if both the name and alternative
|
||||
* name are null
|
||||
* @see #getOption(String, long)
|
||||
*/
|
||||
public long getOption(String name, String alternativeName, long defaultValue) {
|
||||
return Strings.asLong(getOption(name, alternativeName, null), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the integer value associated with the named option, using the supplied default value if none is found or if the
|
||||
* value
|
||||
* cannot be parsed as an integer value.
|
||||
* <p>
|
||||
* The supplied option name is trimmed before matching against the command line arguments. Note that any special characters
|
||||
* such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or the name is null
|
||||
* @see #getOption(String, String, int)
|
||||
*/
|
||||
public int getOption(String name, int defaultValue) {
|
||||
return getOption(name, null, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the integer value associated with the option given the name and alternative name of the option, using the supplied
|
||||
* default value if none is found or if the value cannot be parsed as an integer value.
|
||||
* <p>
|
||||
* The supplied option name and alternative names are trimmed before matching against the command line arguments. Note that
|
||||
* any special characters such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param alternativeName an alternative name for the option; may be null
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or if both the name and alternative
|
||||
* name are null
|
||||
* @see #getOption(String, int)
|
||||
*/
|
||||
public int getOption(String name, String alternativeName, int defaultValue) {
|
||||
return Strings.asInt(getOption(name, alternativeName, null), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the boolean value associated with the named option, using the supplied default value if none is found or if the
|
||||
* value
|
||||
* cannot be parsed as a boolean value.
|
||||
* <p>
|
||||
* The supplied option name is trimmed before matching against the command line arguments. Note that any special characters
|
||||
* such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or the name is null
|
||||
* @see #getOption(String, String, boolean)
|
||||
*/
|
||||
public boolean getOption(String name, boolean defaultValue) {
|
||||
return getOption(name, null, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the boolean value associated with the option given the name and alternative name of the option, using the supplied
|
||||
* default value if none is found or if the value cannot be parsed as a boolean value.
|
||||
* <p>
|
||||
* The supplied option name and alternative names are trimmed before matching against the command line arguments. Note that
|
||||
* any special characters such as prefixes (e.g., '{@code -}' or '{@code --}') must be included in the name.
|
||||
*
|
||||
* @param name the name for the option (e.g., "-v" or "--verbose")
|
||||
* @param alternativeName an alternative name for the option; may be null
|
||||
* @param defaultValue the value that should be returned no named option was found
|
||||
* @return the value associated with the option, or the default value if none was found or if both the name and alternative
|
||||
* name are null
|
||||
* @see #getOption(String, boolean)
|
||||
*/
|
||||
public boolean getOption(String name, String alternativeName, boolean defaultValue) {
|
||||
return Strings.asBoolean(getOption(name, alternativeName, null), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the parameter at the given index. Parameters are those arguments that are not preceded by an option name.
|
||||
*
|
||||
* @param index the index of the parameter
|
||||
* @return the parameter value, or null if there was no parameter at the given index
|
||||
*/
|
||||
public String getParameter(int index) {
|
||||
return index < 0 || index >= params.size() ? null : params.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the specified value matches one of the parameters.
|
||||
*
|
||||
* @param value the parameter value to match
|
||||
* @return true if one of the parameter matches the value, or false otherwise
|
||||
*/
|
||||
public boolean hasParameter(String value) {
|
||||
return value == null || params.isEmpty() ? false : params.contains(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether there were any unknown option names after all possible options have been checked via one of the
|
||||
* {@code getOption(String,...)} methods.
|
||||
*
|
||||
* @return true if there was at least one option (e.g., beginning with '{@code -}') that was not checked, or false
|
||||
* if there were no unknown options on the command line
|
||||
* @see #getFirstUnknownOptionName()
|
||||
* @see #hasUnknowns()
|
||||
*/
|
||||
public boolean hasUnknowns() {
|
||||
return !orderedOptionNames.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of unknown option names after all possible options have been checked via one of the
|
||||
* {@code getOption(String,...)} methods.
|
||||
*
|
||||
* @return the list of options (e.g., beginning with '{@code -}') that were not checked; never null but possible empty
|
||||
* @see #getFirstUnknownOptionName()
|
||||
* @see #hasUnknowns()
|
||||
*/
|
||||
public List<String> getUnknownOptionNames() {
|
||||
return Collections.unmodifiableList(orderedOptionNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* If there were {@link #hasUnknowns() unknown options}, return the first one that appeared on the command line.
|
||||
* @return the first unknown option, or null if there were no {@link #hasUnknowns() unknown options}
|
||||
* @see #getUnknownOptionNames()
|
||||
* @see #getFirstUnknownOptionName()
|
||||
*/
|
||||
public String getFirstUnknownOptionName() {
|
||||
return orderedOptionNames.isEmpty() ? null : orderedOptionNames.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
options.forEach((opt, val) -> {
|
||||
joiner.add("-" + opt).add(val);
|
||||
});
|
||||
params.forEach(joiner::add);
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
}
|
76
debezium-core/src/main/java/io/debezium/util/HashCode.java
Normal file
76
debezium-core/src/main/java/io/debezium/util/HashCode.java
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* Utilities for easily computing hash codes. The algorithm should generally produce good distributions for use in hash-based
|
||||
* containers or collections, but as expected does always result in repeatable hash codes given the inputs.
|
||||
*/
|
||||
@Immutable
|
||||
public class HashCode {
|
||||
|
||||
// Prime number used in improving distribution
|
||||
private static final int PRIME = 103;
|
||||
|
||||
/**
|
||||
* Compute a combined hash code from the supplied objects. This method always returns 0 if no objects are supplied.
|
||||
*
|
||||
* @param objects the objects that should be used to compute the hash code
|
||||
* @return the hash code
|
||||
*/
|
||||
public static int compute(Object... objects) {
|
||||
return _compute(0, objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a combined hash code from the supplied objects using the supplied seed.
|
||||
*
|
||||
* @param seed a value upon which the hash code will be based; may be 0
|
||||
* @param objects the objects that should be used to compute the hash code
|
||||
* @return the hash code
|
||||
*/
|
||||
private static int _compute(int seed,
|
||||
Object... objects) {
|
||||
if (objects == null || objects.length == 0) {
|
||||
return seed * HashCode.PRIME;
|
||||
}
|
||||
// Compute the hash code for all of the objects ...
|
||||
int hc = seed;
|
||||
for (Object object : objects) {
|
||||
hc = HashCode.PRIME * hc;
|
||||
if (object instanceof byte[]) {
|
||||
hc += Arrays.hashCode((byte[]) object);
|
||||
} else if (object instanceof boolean[]) {
|
||||
hc += Arrays.hashCode((boolean[]) object);
|
||||
} else if (object instanceof short[]) {
|
||||
hc += Arrays.hashCode((short[]) object);
|
||||
} else if (object instanceof int[]) {
|
||||
hc += Arrays.hashCode((int[]) object);
|
||||
} else if (object instanceof long[]) {
|
||||
hc += Arrays.hashCode((long[]) object);
|
||||
} else if (object instanceof float[]) {
|
||||
hc += Arrays.hashCode((float[]) object);
|
||||
} else if (object instanceof double[]) {
|
||||
hc += Arrays.hashCode((double[]) object);
|
||||
} else if (object instanceof char[]) {
|
||||
hc += Arrays.hashCode((char[]) object);
|
||||
} else if (object instanceof Object[]) {
|
||||
hc += Arrays.hashCode((Object[]) object);
|
||||
} else if (object != null) {
|
||||
hc += object.hashCode();
|
||||
}
|
||||
}
|
||||
return hc;
|
||||
}
|
||||
|
||||
private HashCode() {
|
||||
}
|
||||
|
||||
}
|
428
debezium-core/src/main/java/io/debezium/util/IoUtil.java
Normal file
428
debezium-core/src/main/java/io/debezium/util/IoUtil.java
Normal file
@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* A set of utilities for more easily performing I/O.
|
||||
*/
|
||||
@Immutable
|
||||
public class IoUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(IoUtil.class);
|
||||
|
||||
/**
|
||||
* Read and return the entire contents of the supplied {@link InputStream stream}. This method always closes the stream when
|
||||
* finished reading.
|
||||
*
|
||||
* @param stream the stream to the contents; may be null
|
||||
* @return the contents, or an empty byte array if the supplied reader is null
|
||||
* @throws IOException if there is an error reading the content
|
||||
*/
|
||||
public static byte[] readBytes(InputStream stream) throws IOException {
|
||||
if (stream == null) return new byte[] {};
|
||||
byte[] buffer = new byte[1024];
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
int numRead = 0;
|
||||
while ((numRead = stream.read(buffer)) > -1) {
|
||||
output.write(buffer, 0, numRead);
|
||||
}
|
||||
output.flush();
|
||||
return output.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the entire contents of the supplied {@link File file}.
|
||||
*
|
||||
* @param file the file containing the contents; may be null
|
||||
* @return the contents, or an empty byte array if the supplied file is null
|
||||
* @throws IOException if there is an error reading the content
|
||||
*/
|
||||
public static byte[] readBytes(File file) throws IOException {
|
||||
if (file == null) return new byte[] {};
|
||||
try (InputStream stream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return readBytes(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the lines from the content of the resource file at the given path on the classpath.
|
||||
*
|
||||
* @param resourcePath the logical path to the classpath, file, or URL resource
|
||||
* @param classLoader the classloader that should be used to load the resource as a stream; may be null
|
||||
* @param clazz the class that should be used to load the resource as a stream; may be null
|
||||
* @param lineProcessor the function that this method calls for each line read from the supplied stream; may not be null
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void readLines(String resourcePath, ClassLoader classLoader, Class<?> clazz, Consumer<String> lineProcessor)
|
||||
throws IOException {
|
||||
try (InputStream stream = IoUtil.getResourceAsStream(resourcePath, classLoader, clazz, null, null)) {
|
||||
IoUtil.readLines(stream, lineProcessor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the lines from the supplied stream. This function completely reads the stream and therefore closes the stream.
|
||||
*
|
||||
* @param stream the stream with the contents to be read; may not be null
|
||||
* @param lineProcessor the function that this method calls for each line read from the supplied stream; may not be null
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void readLines(InputStream stream, Consumer<String> lineProcessor) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lineProcessor.accept(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the lines from the supplied stream. This function completely reads the stream and therefore closes the stream.
|
||||
*
|
||||
* @param path path to the file with the contents to be read; may not be null
|
||||
* @param lineProcessor the function that this method calls for each line read from the supplied stream; may not be null
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void readLines(Path path, Consumer<String> lineProcessor) throws IOException {
|
||||
Files.lines(path).forEach(lineProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the entire contents of the supplied {@link Reader}. This method always closes the reader when finished
|
||||
* reading.
|
||||
*
|
||||
* @param reader the reader of the contents; may be null
|
||||
* @return the contents, or an empty string if the supplied reader is null
|
||||
* @throws IOException if there is an error reading the content
|
||||
*/
|
||||
public static String read(Reader reader) throws IOException {
|
||||
if (reader == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (Reader r = reader) {
|
||||
int numRead = 0;
|
||||
char[] buffer = new char[1024];
|
||||
while ((numRead = reader.read(buffer)) > -1) {
|
||||
sb.append(buffer, 0, numRead);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the entire contents of the supplied {@link InputStream}. This method always closes the stream when finished
|
||||
* reading.
|
||||
*
|
||||
* @param stream the streamed contents; may be null
|
||||
* @return the contents, or an empty string if the supplied stream is null
|
||||
* @throws IOException if there is an error reading the content
|
||||
*/
|
||||
public static String read(InputStream stream) throws IOException {
|
||||
return stream == null ? "" : read(new InputStreamReader(stream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the entire contents of the supplied {@link InputStream}. This method always closes the stream when finished
|
||||
* reading.
|
||||
*
|
||||
* @param stream the streamed contents; may be null
|
||||
* @param charset charset of the stream data; may not be null
|
||||
* @return the contents, or an empty string if the supplied stream is null
|
||||
* @throws IOException if there is an error reading the content
|
||||
*/
|
||||
public static String read(InputStream stream,
|
||||
String charset) throws IOException {
|
||||
return stream == null ? "" : read(new InputStreamReader(stream, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the entire contents of the supplied {@link File}.
|
||||
*
|
||||
* @param file the file containing the information to be read; may be null
|
||||
* @return the contents, or an empty string if the supplied reader is null
|
||||
* @throws IOException if there is an error reading the content
|
||||
*/
|
||||
public static String read(File file) throws IOException {
|
||||
if (file == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (Reader reader = new FileReader(file)) {
|
||||
int numRead = 0;
|
||||
char[] buffer = new char[1024];
|
||||
while ((numRead = reader.read(buffer)) > -1) {
|
||||
sb.append(buffer, 0, numRead);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link InputStream input stream} to the resource given by the supplied path. This method performs these operations
|
||||
* in order, returning as soon as a file is found:
|
||||
* <ol>
|
||||
* <li>look for a file on the file system at the given absolute path; otherwise</li>
|
||||
* <li>look for a file on the file system at the given path relative to the JVM process; otherwise</li>
|
||||
* <li>if a {@code classloader} is supplied, use it to load the file on the classpath at the given path; otherwise</li>
|
||||
* <li>if a {@code clazz} is supplied, use it to load the file on its classpath at the given path; otherwise</li>
|
||||
* <li>try to convert the path to a URL and obtain the referenced resource</li>
|
||||
* </ol>
|
||||
* If all of these fail, this method returns null.
|
||||
*
|
||||
* @param resourcePath the logical path to the classpath, file, or URL resource
|
||||
* @param classLoader the classloader that should be used to load the resource as a stream; may be null
|
||||
* @param clazz the class that should be used to load the resource as a stream; may be null
|
||||
* @param resourceDesc the description of the resource to be used in messages sent to {@code logger}; may be null
|
||||
* @param logger a function that is to be called with log messages describing what is being tried; may be null
|
||||
* @return an input stream to the resource; or null if the resource could not be found
|
||||
* @throws IllegalArgumentException if the resource path is null or empty
|
||||
*/
|
||||
public static InputStream getResourceAsStream(String resourcePath,
|
||||
ClassLoader classLoader,
|
||||
Class<?> clazz, String resourceDesc, Consumer<String> logger) {
|
||||
if (resourcePath == null) throw new IllegalArgumentException("resourcePath may not be null");
|
||||
if (resourceDesc == null && logger != null) resourceDesc = resourcePath;
|
||||
InputStream result = null;
|
||||
if (result == null) {
|
||||
try {
|
||||
// Try absolute path ...
|
||||
Path filePath = FileSystems.getDefault().getPath(resourcePath).toAbsolutePath();
|
||||
File f = filePath.toFile();
|
||||
if (f.exists() && f.isFile() && f.canRead()) {
|
||||
result = new BufferedInputStream(new FileInputStream(f));
|
||||
}
|
||||
logMessage(result, logger, resourceDesc, "on filesystem at " + filePath);
|
||||
} catch (InvalidPathException e) {
|
||||
// just continue ...
|
||||
} catch (FileNotFoundException e) {
|
||||
// just continue ...
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
try {
|
||||
// Try relative to current working directory ...
|
||||
Path current = FileSystems.getDefault().getPath(".").toAbsolutePath();
|
||||
Path absolute = current.resolve(Paths.get(resourcePath)).toAbsolutePath();
|
||||
File f = absolute.toFile();
|
||||
if (f.exists() && f.isFile() && f.canRead()) {
|
||||
result = new BufferedInputStream(new FileInputStream(f));
|
||||
}
|
||||
logMessage(result, logger, resourceDesc, "on filesystem relative to '" + current + "' at '" + absolute + "'");
|
||||
} catch (InvalidPathException e) {
|
||||
// just continue ...
|
||||
} catch (FileNotFoundException e) {
|
||||
// just continue ...
|
||||
}
|
||||
}
|
||||
if (result == null && classLoader != null) {
|
||||
// Try using the class loader ...
|
||||
result = classLoader.getResourceAsStream(resourcePath);
|
||||
logMessage(result, logger, resourceDesc, "on classpath");
|
||||
}
|
||||
if (result == null && clazz != null) {
|
||||
// Not yet found, so try the class ...
|
||||
result = clazz.getResourceAsStream(resourcePath);
|
||||
if (result == null) {
|
||||
// Not yet found, so try the class's class loader ...
|
||||
result = clazz.getClassLoader().getResourceAsStream(resourcePath);
|
||||
}
|
||||
logMessage(result, logger, resourceDesc, "on classpath");
|
||||
}
|
||||
if (result == null) {
|
||||
// Still not found, so try to construct a URL out of it ...
|
||||
try {
|
||||
URL url = new URL(resourcePath);
|
||||
result = url.openStream();
|
||||
logMessage(result, logger, resourceDesc, "at URL " + url.toExternalForm());
|
||||
} catch (MalformedURLException e) {
|
||||
// just continue ...
|
||||
} catch (IOException err) {
|
||||
// just continue ...
|
||||
}
|
||||
}
|
||||
// May be null ...
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory at the given absolute or relative path.
|
||||
*
|
||||
* @param path the relative or absolute path of the directory; may not be null
|
||||
* @return the reference to the existing readable and writable directory
|
||||
*/
|
||||
public static File createDirectory(Path path) {
|
||||
File dir = path.toAbsolutePath().toFile();
|
||||
if (dir.exists() && dir.canRead() && dir.canWrite()) {
|
||||
if (dir.isDirectory()) return dir;
|
||||
throw new IllegalStateException("Expecting '" + path + "' to be a directory but found a file");
|
||||
}
|
||||
dir.mkdirs();
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file at the given absolute or relative path.
|
||||
*
|
||||
* @param path the relative or absolute path of the file to create; may not be null
|
||||
* @return the reference to the existing readable and writable file
|
||||
*/
|
||||
public static File createFile(Path path) {
|
||||
File file = path.toAbsolutePath().toFile();
|
||||
if (file.exists() && file.canRead() && file.canWrite()) {
|
||||
if (file.isFile()) return file;
|
||||
throw new IllegalStateException("Expecting '" + path + "' to be a file but found a directory");
|
||||
}
|
||||
file.getParentFile().mkdirs();
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory at the given absolute or relative path, removing any existing content beforehand.
|
||||
*
|
||||
* @param path the relative or absolute path of the directory to recreate; may not be null
|
||||
* @param removeExistingContent true if any existing content should be removed
|
||||
* @return the reference to the existing readable and writable directory
|
||||
* @throws IOException if there is a problem deleting the files at this path
|
||||
*/
|
||||
public static File createDirectory(Path path, boolean removeExistingContent) throws IOException {
|
||||
File dir = path.toAbsolutePath().toFile();
|
||||
if (dir.exists() && dir.canRead() && dir.canWrite()) {
|
||||
if (dir.isDirectory()) {
|
||||
delete(path);
|
||||
return dir;
|
||||
}
|
||||
throw new IllegalStateException("Expecting '" + path + "' to be a directory but found a file");
|
||||
}
|
||||
dir.mkdirs();
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that will delete a file or folder only if it is within the 'target' directory (for safety).
|
||||
* Folders are removed recursively.
|
||||
*
|
||||
* @param path the path to the file or folder in the target directory
|
||||
* @throws IOException if there is a problem deleting the file at the given path
|
||||
*/
|
||||
public static void delete(String path) throws IOException {
|
||||
if (path != null) delete(Paths.get(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that will delete a file or folder. Folders are removed recursively.
|
||||
*
|
||||
* @param fileOrFolder the file or folder to be deleted
|
||||
* @throws IOException if there is a problem deleting the file at the given path
|
||||
*/
|
||||
public static void delete(File fileOrFolder) throws IOException {
|
||||
if (fileOrFolder != null) delete(fileOrFolder.toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that will delete multiple file and/or folders. Folders are removed recursively.
|
||||
*
|
||||
* @param filesOrFolder the files and folders to be deleted
|
||||
* @throws IOException if there is a problem deleting the file at the given path
|
||||
*/
|
||||
public static void delete(File... filesOrFolder) throws IOException {
|
||||
for (File fileOrFolder : filesOrFolder) {
|
||||
delete(fileOrFolder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method that will recursively delete a file or folder.
|
||||
*
|
||||
* @param path the path to the file or folder in the target directory
|
||||
* @throws IOException if there is a problem deleting the file at the given path
|
||||
*/
|
||||
public static void delete(Path path) throws IOException {
|
||||
if (path != null) {
|
||||
if (path.toAbsolutePath().toFile().exists()) {
|
||||
LOGGER.info("Deleting '" + path + "'...");
|
||||
Set<FileVisitOption> options = EnumSet.noneOf(FileVisitOption.class);
|
||||
int maxDepth = 10;
|
||||
FileVisitor<Path> removingVisitor = new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
java.nio.file.Files.delete(file);
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
java.nio.file.Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
|
||||
LOGGER.error("Unable to remove '{}'", file.getFileName(), exc);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
};
|
||||
|
||||
java.nio.file.Files.walkFileTree(path, options, maxDepth, removingVisitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a port that is available. This method starts a {@link ServerSocket} and obtains the port on which the socket is
|
||||
* listening, and then shuts down the socket so the port becomes available.
|
||||
*
|
||||
* @return the number of the now-available port
|
||||
* @throws IllegalStateException if it cannot find an available port
|
||||
*/
|
||||
public static int getAvailablePort() {
|
||||
try (ServerSocket socket = new ServerSocket(0)) {
|
||||
return socket.getLocalPort();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Cannot find available port: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void logMessage(InputStream stream, Consumer<String> logger, String resourceDesc, String msg) {
|
||||
if (stream != null && logger != null) {
|
||||
logger.accept("Found " + resourceDesc + " " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
private IoUtil() {
|
||||
// Prevent construction
|
||||
}
|
||||
}
|
316
debezium-core/src/main/java/io/debezium/util/Iterators.java
Normal file
316
debezium-core/src/main/java/io/debezium/util/Iterators.java
Normal file
@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* A utility for creating iterators.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@Immutable
|
||||
public class Iterators {
|
||||
|
||||
public static <T> Iterator<T> empty() {
|
||||
return new Iterator<T>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> with(final T value) {
|
||||
return new Iterator<T>() {
|
||||
private boolean finished = false;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return !finished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (finished) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
finished = true;
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> with(T value1, T value2) {
|
||||
return new Iterator<T>() {
|
||||
private int remaining = 2;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return remaining > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (remaining == 2) {
|
||||
--remaining;
|
||||
return value1;
|
||||
}
|
||||
if (remaining == 1) {
|
||||
--remaining;
|
||||
return value2;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> with(T value1, T value2, T value3) {
|
||||
return new Iterator<T>() {
|
||||
private int remaining = 3;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return remaining > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (remaining == 3) {
|
||||
--remaining;
|
||||
return value1;
|
||||
}
|
||||
if (remaining == 2) {
|
||||
--remaining;
|
||||
return value2;
|
||||
}
|
||||
if (remaining == 1) {
|
||||
--remaining;
|
||||
return value3;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Iterator<T> with(T value1, T value2, T value3, T... additional) {
|
||||
return new Iterator<T>() {
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < additional.length + 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
try {
|
||||
if (index == 0) return value1;
|
||||
if (index == 1) return value2;
|
||||
if (index == 2) return value3;
|
||||
if (index < additional.length + 3) return additional[index - 3];
|
||||
--index;
|
||||
throw new NoSuchElementException();
|
||||
} finally {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> with(T[] values) {
|
||||
return new Iterator<T>() {
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < values.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
try {
|
||||
if (index < values.length) return values[index];
|
||||
--index;
|
||||
throw new NoSuchElementException();
|
||||
} finally {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T, U, V> Iterator<V> around(Iterable<? extends T> first,
|
||||
Iterable<? extends U> second,
|
||||
BiFunction<T, U, V> conversion) {
|
||||
return around(first.iterator(), second.iterator(), conversion);
|
||||
}
|
||||
|
||||
public static <T, U, V> Iterator<V> around(final Iterator<? extends T> first,
|
||||
final Iterator<? extends U> second,
|
||||
final BiFunction<T, U, V> combineFirstAndSecond) {
|
||||
return new Iterator<V>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return second.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next() {
|
||||
return combineFirstAndSecond.apply(first.next(), second.next());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <V, T> Iterator<T> around(final Iterable<? extends V> iterable, Function<V, T> conversion) {
|
||||
return around(iterable.iterator(), conversion);
|
||||
}
|
||||
|
||||
public static <V, T> Iterator<T> around(final Iterator<? extends V> iterator, Function<V, T> conversion) {
|
||||
return new Iterator<T>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return conversion.apply(iterator.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
iterator.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterable<T> around(final Iterator<T> iterator) {
|
||||
return new Iterable<T>() {
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return iterator;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> readOnly(final Iterator<T> iterator) {
|
||||
return new Iterator<T>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return iterator.next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <V, T> Iterator<T> readOnly(final Iterator<? extends V> iterator, Function<V, T> conversion) {
|
||||
return new Iterator<T>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return conversion.apply(iterator.next());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> readOnly(final Iterable<T> iterable) {
|
||||
return readOnly(iterable.iterator());
|
||||
}
|
||||
|
||||
public static <V, T> Iterator<T> readOnly(final Iterable<V> iterable, Function<V, T> conversion) {
|
||||
return readOnly(iterable.iterator(), conversion);
|
||||
}
|
||||
|
||||
public static <T> Iterable<T> readOnlyIterable(final Iterable<T> iterable) {
|
||||
return new Iterable<T>() {
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return readOnly(iterable.iterator());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <V, T> Iterable<T> readOnlyIterable(final Iterable<? extends V> iterable, Function<V, T> conversion) {
|
||||
return new Iterable<T>() {
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return readOnly(iterable.iterator(), conversion);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> join(Iterable<T> first, T last) {
|
||||
return join(first.iterator(), with(last));
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> join(Iterable<T> first, T last1, T last2) {
|
||||
return join(first.iterator(), with(last1, last2));
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> join(Iterable<T> first, T last1, T last2, T last3) {
|
||||
return join(first.iterator(), with(last1, last2, last3));
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> join(Iterable<T> first, T last1, T last2, T last3, T last4) {
|
||||
return join(first.iterator(), with(last1, last2, last3, last4));
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> join(Iterable<T> first, Iterable<T> second) {
|
||||
return join(first.iterator(), second.iterator());
|
||||
}
|
||||
|
||||
public static <T> Iterator<T> join(Iterator<T> first, Iterator<T> second) {
|
||||
return new Iterator<T>() {
|
||||
private boolean completedFirst = false;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (!completedFirst) {
|
||||
if (first.hasNext()) return true;
|
||||
completedFirst = true;
|
||||
}
|
||||
return second.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (!completedFirst) {
|
||||
if (first.hasNext()) return first.next();
|
||||
completedFirst = true;
|
||||
}
|
||||
return second.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (!completedFirst) {
|
||||
first.remove();
|
||||
}
|
||||
second.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
80
debezium-core/src/main/java/io/debezium/util/Joiner.java
Normal file
80
debezium-core/src/main/java/io/debezium/util/Joiner.java
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* A utility for joining multiple {@link CharSequence character sequences} together.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@Immutable
|
||||
public final class Joiner {
|
||||
|
||||
public static Joiner on(CharSequence delimiter) {
|
||||
return new Joiner(new StringJoiner(delimiter));
|
||||
}
|
||||
|
||||
public static Joiner on(CharSequence prefix, CharSequence delimiter) {
|
||||
return new Joiner(new StringJoiner(delimiter, prefix, ""));
|
||||
}
|
||||
|
||||
public static Joiner on(CharSequence prefix, CharSequence delimiter, CharSequence suffix) {
|
||||
return new Joiner(new StringJoiner(delimiter, prefix, suffix));
|
||||
}
|
||||
|
||||
private final StringJoiner joiner;
|
||||
|
||||
private Joiner(StringJoiner joiner) {
|
||||
this.joiner = joiner;
|
||||
}
|
||||
|
||||
public String join(Object[] values) {
|
||||
for (Object value : values) {
|
||||
if (value != null) joiner.add(value.toString());
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
public String join(CharSequence firstValue, CharSequence... additionalValues) {
|
||||
if (firstValue != null) joiner.add(firstValue);
|
||||
for (CharSequence value : additionalValues) {
|
||||
if (value != null) joiner.add(value);
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
public String join(Iterable<?> values) {
|
||||
for (Object value : values) {
|
||||
if (value != null) joiner.add(value.toString());
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
public String join(Iterable<?> values, CharSequence nextValue, CharSequence... additionalValues) {
|
||||
for (Object value : values) {
|
||||
if (value != null) joiner.add(value.toString());
|
||||
}
|
||||
if (nextValue != null) joiner.add(nextValue);
|
||||
for (CharSequence value : additionalValues) {
|
||||
if (value != null) joiner.add(value);
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
public String join(Iterator<?> values) {
|
||||
while (values.hasNext()) {
|
||||
Object value = values.next();
|
||||
if (value != null) joiner.add(value.toString());
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.debezium.annotation.ThreadSafe;
|
||||
|
||||
/**
|
||||
* An atomic reference that atomically uses a supplier to lazily accesses the referenced object the first time it is needed.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
* @param <T> the type of referenced object
|
||||
*/
|
||||
@ThreadSafe
|
||||
public final class LazyReference<T> {
|
||||
|
||||
public static <T> LazyReference<T> create(Supplier<T> supplier) {
|
||||
return new LazyReference<T>(supplier);
|
||||
}
|
||||
|
||||
private final AtomicReference<T> ref = new AtomicReference<>();
|
||||
private final Supplier<T> supplier;
|
||||
|
||||
private LazyReference(Supplier<T> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the referenced object has been created and accessed.
|
||||
*
|
||||
* @return {@code true} if the object has been created, or false otherwise
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return ref.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the referenced object has been {@link #isInitialized() initialized}, then release it.
|
||||
* This method does nothing if the reference has not yet been accessed or {@link #isInitialized() initialized}.
|
||||
*/
|
||||
public void release() {
|
||||
release(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the referenced object has been {@link #isInitialized() initialized}, then release it and call the supplied function with
|
||||
* the reference. This method does nothing if the reference has not yet been accessed or {@link #isInitialized() initialized}.
|
||||
*
|
||||
* @param finalizer the function that should be called when the previously-{@link #isInitialized() initialized} referenced
|
||||
* object is released; may be null
|
||||
*/
|
||||
public void release(Consumer<T> finalizer) {
|
||||
ref.updateAndGet(existing->{
|
||||
if ( existing != null && finalizer != null ) finalizer.accept(existing);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the referenced value (creating it if required) and call the supplied function.
|
||||
*
|
||||
* @param consumer the function that operates on the value; may not be null
|
||||
* @return true if the function was called on the referenced value, or false if there is no referenced value
|
||||
*/
|
||||
public boolean execute(Consumer<T> consumer) {
|
||||
T value = get();
|
||||
if (value == null) return false;
|
||||
consumer.accept(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public T get() {
|
||||
return ref.updateAndGet(existing->existing != null ? existing : supplier.get());
|
||||
}
|
||||
}
|
721
debezium-core/src/main/java/io/debezium/util/MathOps.java
Normal file
721
debezium-core/src/main/java/io/debezium/util/MathOps.java
Normal file
@ -0,0 +1,721 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* Utilities for performing math operations with mixed native and advanced numeric types.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@Immutable
|
||||
public final class MathOps {
|
||||
|
||||
public static Number add(Number first, Number second) {
|
||||
if (second == null)
|
||||
return first;
|
||||
else if (first == null) return second;
|
||||
if (first instanceof Short) return add((Short) first, second);
|
||||
if (first instanceof Integer) return add((Integer) first, second);
|
||||
if (first instanceof Long) return add((Long) first, second);
|
||||
if (first instanceof Float) return add((Float) first, second);
|
||||
if (first instanceof Double) return add((Double) first, second);
|
||||
if (first instanceof BigInteger) return add((BigInteger) first, second);
|
||||
if (first instanceof BigDecimal) return add((BigDecimal) first, second);
|
||||
if (first instanceof AtomicLong) return add((AtomicLong) first, second);
|
||||
if (first instanceof AtomicInteger) return add((AtomicInteger) first, second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(Short first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(Short first, short second) {
|
||||
int sum = first.shortValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
return new Integer(sum);
|
||||
}
|
||||
|
||||
public static Number add(Short first, int second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Short first, long second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Short first, float second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Short first, double second) {
|
||||
double sum = first.doubleValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Short first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(Short first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Short first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Short first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(Short first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(Short first, BigDecimal second) {
|
||||
return second.add(BigDecimal.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Short first, BigInteger second) {
|
||||
return second.add(BigInteger.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Short first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Short first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Integer first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(Integer first, short second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Integer first, int second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Integer first, long second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Integer first, float second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Integer first, double second) {
|
||||
double sum = first.doubleValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Integer first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(Integer first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Integer first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Integer first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(Integer first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(Integer first, BigDecimal second) {
|
||||
return second.add(BigDecimal.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Integer first, BigInteger second) {
|
||||
return second.add(BigInteger.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Integer first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Integer first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Long first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(Long first, short second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Long first, int second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Long first, long second) {
|
||||
long sum = first.longValue() + second;
|
||||
if (Short.MAX_VALUE >= sum && Short.MIN_VALUE <= sum) return new Short((short) sum);
|
||||
if (Integer.MAX_VALUE >= sum && Integer.MIN_VALUE <= sum) return new Integer((int) sum);
|
||||
return new Long(sum);
|
||||
}
|
||||
|
||||
public static Number add(Long first, float second) {
|
||||
double sum = first.doubleValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Long first, double second) {
|
||||
double sum = first.doubleValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Long first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(Long first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Long first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Long first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(Long first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(Long first, BigDecimal second) {
|
||||
return second.add(BigDecimal.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Long first, BigInteger second) {
|
||||
return second.add(BigInteger.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Long first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Long first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Float first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(Float first, short second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Float first, int second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Float first, long second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Float first, float second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Float first, double second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Float first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(Float first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Float first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Float first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(Float first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(Float first, BigDecimal second) {
|
||||
return second.add(BigDecimal.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Float first, BigInteger second) {
|
||||
return second.add(BigInteger.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Float first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Float first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Double first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(Double first, short second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Double first, int second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Double first, long second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Double first, float second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Double first, double second) {
|
||||
double sum = first.floatValue() + second;
|
||||
if (Float.MAX_VALUE >= sum && Float.MIN_VALUE <= sum) return new Float((float) sum);
|
||||
return new Double(sum);
|
||||
}
|
||||
|
||||
public static Number add(Double first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(Double first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Double first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(Double first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(Double first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(Double first, BigDecimal second) {
|
||||
return second.add(BigDecimal.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Double first, BigInteger second) {
|
||||
return second.add(BigInteger.valueOf(first.longValue()));
|
||||
}
|
||||
|
||||
public static Number add(Double first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(Double first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, short second) {
|
||||
return first.add(BigInteger.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, int second) {
|
||||
return first.add(BigInteger.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, long second) {
|
||||
return first.add(BigInteger.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, float second) {
|
||||
return new BigDecimal(first).add(BigDecimal.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, double second) {
|
||||
return new BigDecimal(first).add(BigDecimal.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, BigDecimal second) {
|
||||
return second.add(new BigDecimal(first));
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, BigInteger second) {
|
||||
return second.add(second);
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(BigInteger first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, short second) {
|
||||
return first.add(BigDecimal.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, int second) {
|
||||
return first.add(BigDecimal.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, long second) {
|
||||
return first.add(BigDecimal.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, float second) {
|
||||
return first.add(BigDecimal.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, double second) {
|
||||
return first.add(BigDecimal.valueOf(second));
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, BigDecimal second) {
|
||||
return second.add(first);
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, BigInteger second) {
|
||||
return second.add(second);
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(BigDecimal first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, short second) {
|
||||
return add(new Integer(first.intValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, int second) {
|
||||
return add(new Integer(first.intValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, long second) {
|
||||
return add(new Integer(first.intValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, float second) {
|
||||
return add(new Integer(first.intValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, double second) {
|
||||
return add(new Integer(first.intValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, BigDecimal second) {
|
||||
return add(second, first);
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, BigInteger second) {
|
||||
return add(second, first);
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicInteger first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, Number second) {
|
||||
if (second instanceof Short) return add(first, (Short) second);
|
||||
if (second instanceof Integer) return add(first, (Integer) second);
|
||||
if (second instanceof Long) return add(first, (Long) second);
|
||||
if (second instanceof Float) return add(first, (Float) second);
|
||||
if (second instanceof Double) return add(first, (Double) second);
|
||||
if (second instanceof BigInteger) return add(first, (BigInteger) second);
|
||||
if (second instanceof BigDecimal) return add(first, (BigDecimal) second);
|
||||
if (second instanceof AtomicInteger) return add(first, (AtomicInteger) second);
|
||||
if (second instanceof AtomicLong) return add(first, (AtomicLong) second);
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, short second) {
|
||||
return add(new Long(first.longValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, int second) {
|
||||
return add(new Long(first.longValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, long second) {
|
||||
return add(new Long(first.longValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, float second) {
|
||||
return add(new Long(first.longValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, double second) {
|
||||
return add(new Long(first.longValue()), second);
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, Short second) {
|
||||
return add(first, second.shortValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, Integer second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, Long second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, Float second) {
|
||||
return add(first, second.floatValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, Double second) {
|
||||
return add(first, second.doubleValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, BigDecimal second) {
|
||||
return add(second, first);
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, BigInteger second) {
|
||||
return add(second, first);
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, AtomicInteger second) {
|
||||
return add(first, second.intValue());
|
||||
}
|
||||
|
||||
public static Number add(AtomicLong first, AtomicLong second) {
|
||||
return add(first, second.longValue());
|
||||
}
|
||||
|
||||
private MathOps() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.debezium.annotation.ThreadSafe;
|
||||
|
||||
/**
|
||||
* A simple named thread factory that creates threads named "{@code $PREFIX$-$NAME$-thread-$NUMBER$}".
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@ThreadSafe
|
||||
public final class NamedThreadFactory implements ThreadFactory {
|
||||
|
||||
private static final boolean DEFAULT_DAEMON_THREAD = true;
|
||||
private static final int DEFAULT_STACK_SIZE = 0;
|
||||
|
||||
private final boolean daemonThreads;
|
||||
private final ThreadGroup group;
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String namePrefix;
|
||||
private final int stackSize;
|
||||
private final Consumer<String> afterThreadCreation;
|
||||
|
||||
public NamedThreadFactory(String prefix) {
|
||||
this(prefix, DEFAULT_DAEMON_THREAD, DEFAULT_STACK_SIZE);
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String prefix, boolean daemonThreads) {
|
||||
this(prefix, daemonThreads, DEFAULT_STACK_SIZE);
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String prefix, boolean daemonThreads, int stackSize) {
|
||||
this(prefix, daemonThreads, stackSize, null);
|
||||
}
|
||||
|
||||
public NamedThreadFactory(String prefix, boolean daemonThreads, int stackSize, Consumer<String> afterThreadCreation) {
|
||||
if ( prefix == null ) throw new IllegalArgumentException("The thread prefix may not be null");
|
||||
final SecurityManager s = System.getSecurityManager();
|
||||
this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
|
||||
this.namePrefix = prefix;
|
||||
this.daemonThreads = daemonThreads;
|
||||
this.stackSize = stackSize;
|
||||
this.afterThreadCreation = afterThreadCreation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(final Runnable runnable) {
|
||||
String threadName = namePrefix + threadNumber.getAndIncrement();
|
||||
final Thread t = new Thread(group, runnable, threadName, stackSize);
|
||||
t.setDaemon(daemonThreads);
|
||||
if (t.getPriority() != Thread.NORM_PRIORITY) {
|
||||
t.setPriority(Thread.NORM_PRIORITY);
|
||||
}
|
||||
if (afterThreadCreation != null) {
|
||||
try {
|
||||
afterThreadCreation.accept(threadName);
|
||||
} catch (Throwable e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
99
debezium-core/src/main/java/io/debezium/util/Sequences.java
Normal file
99
debezium-core/src/main/java/io/debezium/util/Sequences.java
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import io.debezium.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* Utility methods for obtaining streams of integers.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@Immutable
|
||||
public class Sequences {
|
||||
|
||||
/**
|
||||
* Create a stream of <em>number</em> monotonically increasing numbers starting at 0, useful when performing an operation
|
||||
* <em>number</em> times.
|
||||
*
|
||||
* @param number the number of values to include in the stream; must be positive
|
||||
* @return the sequence; never null
|
||||
*/
|
||||
public static IntStream times(int number) {
|
||||
return IntStream.range(0, number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an iterator over an infinite number of monotonically increasing numbers starting at 0, useful when performing an
|
||||
* operation an unknown number of times.
|
||||
*
|
||||
* @return the sequence; never null
|
||||
*/
|
||||
public static Iterable<Integer> infiniteIntegers() {
|
||||
return infiniteIntegers(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an iterator over an infinite number monotonically increasing numbers starting at the given number, useful when
|
||||
* performing an operation an unknown number of times.
|
||||
*
|
||||
* @param startingAt the first number to include in the resulting stream
|
||||
* @return the sequence; never null
|
||||
*/
|
||||
public static Iterable<Integer> infiniteIntegers(int startingAt) {
|
||||
return Iterators.around(new Iterator<Integer>() {
|
||||
private int counter = startingAt;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer next() {
|
||||
return Integer.valueOf(counter++);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a supplier function that randomly selects from the given values. If the supplied values contain nulls, then
|
||||
* the resulting supplier function may return null values.
|
||||
*
|
||||
* @param first the first value that may be randomly picked
|
||||
* @param additional the additional values to randomly pick from; may be null or empty
|
||||
* @return the supplier function; never null
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Supplier<T> randomlySelect(T first, T... additional) {
|
||||
if (additional == null || additional.length == 0) return () -> first;
|
||||
Random rng = new Random(System.currentTimeMillis());
|
||||
int max = additional.length + 1;
|
||||
return () -> {
|
||||
int index = rng.nextInt(max);
|
||||
return index == 0 ? first : additional[index-1];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a supplier function that randomly selects from the given values. If the supplied values contain nulls, then
|
||||
* the resulting supplier function may return null values.
|
||||
*
|
||||
* @param values the values to randomly pick from; may not be null, should not be empty
|
||||
* @return the supplier function; never null
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Supplier<T> randomlySelect(T... values) {
|
||||
if (values == null || values.length == 0) throw new IllegalArgumentException("The values array may not be null or empty");
|
||||
Random rng = new Random(System.currentTimeMillis());
|
||||
return () -> values[rng.nextInt(values.length)];
|
||||
}
|
||||
}
|
521
debezium-core/src/main/java/io/debezium/util/Stopwatch.java
Normal file
521
debezium-core/src/main/java/io/debezium/util/Stopwatch.java
Normal file
@ -0,0 +1,521 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.LongSummaryStatistics;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.debezium.annotation.ThreadSafe;
|
||||
|
||||
/**
|
||||
* A stopwatch for measuring durations. All NewStopwatch implementations are threadsafe, although using a single stopwatch
|
||||
* object across threads requires caution and care.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@ThreadSafe
|
||||
public abstract class Stopwatch {
|
||||
|
||||
/**
|
||||
* Start the stopwatch. Calling this method on an already-started stopwatch has no effect.
|
||||
*
|
||||
* @return this object to enable chaining methods
|
||||
* @see #stop
|
||||
*/
|
||||
public abstract Stopwatch start();
|
||||
|
||||
/**
|
||||
* Stop the stopwatch. Calling this method on an already-stopped stopwatch has no effect.
|
||||
*
|
||||
* @return this object to enable chaining methods
|
||||
* @see #start()
|
||||
*/
|
||||
public abstract Stopwatch stop();
|
||||
|
||||
/**
|
||||
* Get the total and average durations measured by this stopwatch.
|
||||
*
|
||||
* @return the durations; never null
|
||||
*/
|
||||
public abstract Durations durations();
|
||||
|
||||
/**
|
||||
* The average and total durations as measured by one or more stopwatches.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public static interface Durations {
|
||||
|
||||
/**
|
||||
* Get the statistics for the durations in nanoseconds.
|
||||
*
|
||||
* @return the statistics; never null
|
||||
*/
|
||||
Statistics statistics();
|
||||
}
|
||||
|
||||
/**
|
||||
* The timing statistics for a recorded set of samples.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*
|
||||
*/
|
||||
public static interface Statistics {
|
||||
/**
|
||||
* Returns the count of durations recorded.
|
||||
*
|
||||
* @return the count of durations
|
||||
*/
|
||||
public long getCount();
|
||||
|
||||
/**
|
||||
* Returns the total of all recorded durations.
|
||||
*
|
||||
* @return The total duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
public Duration getTotal();
|
||||
|
||||
/**
|
||||
* Returns the minimum of all recorded durations.
|
||||
*
|
||||
* @return The minimum duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
public Duration getMinimum();
|
||||
|
||||
/**
|
||||
* Returns the maximum of all recorded durations.
|
||||
*
|
||||
* @return The maximum duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
public Duration getMaximum();
|
||||
|
||||
/**
|
||||
* Returns the arithmetic mean of all recorded durations.
|
||||
*
|
||||
* @return The average duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
public Duration getAverage();
|
||||
|
||||
/**
|
||||
* Returns a string representation of the total of all recorded durations.
|
||||
*
|
||||
* @return the string representation of the total duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
default public String getTotalAsString() {
|
||||
return asString(getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the minimum of all recorded durations.
|
||||
*
|
||||
* @return the string representation of the minimum duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
default public String getMinimumAsString() {
|
||||
return asString(getMinimum());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the maximum of all recorded durations.
|
||||
*
|
||||
* @return the string representation of the maximum duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
default public String getMaximumAsString() {
|
||||
return asString(getMaximum());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the arithmetic mean of all recorded durations.
|
||||
*
|
||||
* @return the string representation of the average duration; never null but possibly {@link Duration#ZERO}.
|
||||
*/
|
||||
default public String getAverageAsString() {
|
||||
return asString(getAverage());
|
||||
}
|
||||
}
|
||||
|
||||
private static Statistics createStatistics(LongSummaryStatistics stats) {
|
||||
boolean some = stats.getCount() > 0L;
|
||||
return new Statistics() {
|
||||
@Override
|
||||
public long getCount() {
|
||||
return stats.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaximum() {
|
||||
return some ? Duration.ofNanos(stats.getMax()) : Duration.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMinimum() {
|
||||
return some ? Duration.ofNanos(stats.getMin()) : Duration.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getTotal() {
|
||||
return some ? Duration.ofNanos(stats.getSum()) : Duration.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getAverage() {
|
||||
return some ? Duration.ofNanos((long) stats.getAverage()) : Duration.ZERO;
|
||||
}
|
||||
|
||||
private String fixedLengthSeconds(Duration duration) {
|
||||
double seconds = duration.toNanos() * 1e-9;
|
||||
String result = new DecimalFormat("##0.00000").format(seconds) + "s";
|
||||
if (result.length() == 8) return " " + result;
|
||||
if (result.length() == 9) return " " + result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private String fixedLength(long count) {
|
||||
String result = new DecimalFormat("###0").format(count);
|
||||
if (result.length() == 1) return " " + result;
|
||||
if (result.length() == 2) return " " + result;
|
||||
if (result.length() == 3) return " " + result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(fixedLengthSeconds(getTotal()) + " total;");
|
||||
sb.append(fixedLength(getCount()) + " samples;");
|
||||
sb.append(fixedLengthSeconds(getAverage()) + " avg;");
|
||||
sb.append(fixedLengthSeconds(getMinimum()) + " min;");
|
||||
sb.append(fixedLengthSeconds(getMaximum()) + " max");
|
||||
return sb.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Stopwatch} that can be reused. The resulting {@link Stopwatch#durations()}, however,
|
||||
* only reflect the most recently completed stopwatch interval.
|
||||
* <p>
|
||||
* For example, the following code shows this behavior:
|
||||
*
|
||||
* <pre>
|
||||
* Stopwatch sw = Stopwatch.reusable();
|
||||
* sw.start();
|
||||
* sleep(3000); // sleep 3 seconds
|
||||
* sw.stop();
|
||||
* print(sw.durations()); // total and average duration are each 3 seconds
|
||||
*
|
||||
* sw.start();
|
||||
* sleep(2000); // sleep 2 seconds
|
||||
* sw.stop();
|
||||
* print(sw.durations()); // total and average duration are each 2 seconds
|
||||
* </pre>
|
||||
*
|
||||
* @return the new stopwatch; never null
|
||||
*/
|
||||
public static Stopwatch reusable() {
|
||||
return createWith(new SingleDuration(), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Stopwatch} that records all of the measured durations of the stopwatch.
|
||||
* <p>
|
||||
* For example, the following code shows this behavior:
|
||||
*
|
||||
* <pre>
|
||||
* Stopwatch sw = Stopwatch.accumulating();
|
||||
* sw.start();
|
||||
* sleep(3000); // sleep 3 seconds
|
||||
* sw.stop();
|
||||
* print(sw.durations()); // total and average duration are each 3 seconds
|
||||
*
|
||||
* sw.start();
|
||||
* sleep(2000); // sleep 2 seconds
|
||||
* sw.stop();
|
||||
* print(sw.durations()); // total duration is now 5 seconds, average is 2.5 seconds
|
||||
* </pre>
|
||||
*
|
||||
* @return the new stopwatch; never null
|
||||
*/
|
||||
public static Stopwatch accumulating() {
|
||||
return createWith(new MultipleDurations(), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of stopwatches whose durations are combined. New stopwatches can be created at any time, and when
|
||||
* {@link Stopwatch#stop()} will always record their duration with this set.
|
||||
* <p>
|
||||
* This set is threadsafe, meaning that multiple threads can {@link #create()} new stopwatches concurrently, and each
|
||||
* stopwatch's duration is measured separately. Additionally, all of the other methods of this interface are also threadsafe.
|
||||
* </p>
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@ThreadSafe
|
||||
public static interface StopwatchSet extends Durations {
|
||||
/**
|
||||
* Create a new stopwatch that records durations with this set.
|
||||
*
|
||||
* @return the new stopwatch; never null
|
||||
*/
|
||||
Stopwatch create();
|
||||
|
||||
/**
|
||||
* Time the given function.
|
||||
*
|
||||
* @param runnable the function to call
|
||||
*/
|
||||
default public void time(Runnable runnable) {
|
||||
time(1, runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time the given function.
|
||||
*
|
||||
* @param runnable the function that is to be executed; may not be null
|
||||
* @return the result of the operation
|
||||
*/
|
||||
default public <T> T time(Callable<T> runnable) {
|
||||
Stopwatch sw = create().start();
|
||||
try {
|
||||
return runnable.call();
|
||||
} catch (RuntimeException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
sw.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Time the given function multiple times.
|
||||
*
|
||||
* @param repeat the number of times to repeat the function call; must be positive
|
||||
* @param runnable the function to call; may not be null
|
||||
*/
|
||||
default public void time(int repeat, Runnable runnable) {
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
Stopwatch sw = create().start();
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
sw.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Time the given function multiple times.
|
||||
*
|
||||
* @param repeat the number of times to repeat the function call; must be positive
|
||||
* @param runnable the function that is to be executed a number of times; may not be null
|
||||
* @param cleanup the function that is to be called after each time call to the runnable function, and not included
|
||||
* in the time measurements; may be null
|
||||
* @throws Exception the exception thrown by the runnable function
|
||||
*/
|
||||
default public <T> void time(int repeat, Callable<T> runnable, Consumer<T> cleanup) throws Exception {
|
||||
for (int i = 0; i != repeat; ++i) {
|
||||
T result = null;
|
||||
Stopwatch sw = create().start();
|
||||
try {
|
||||
result = runnable.call();
|
||||
} finally {
|
||||
sw.stop();
|
||||
if (cleanup != null) {
|
||||
cleanup.accept(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until all running stopwatches have been {@link Stopwatch#stop() stopped}. This means that if a stopwatch
|
||||
* is {@link #create() created} but never started, this method will not wait for it. Likewise, if a stopwatch
|
||||
* is {@link #create() created} and started, then this method will block until the stopwatch is {@link Stopwatch#stop()
|
||||
* stopped} (even if the same stopwatch is started multiple times).
|
||||
* are stopped.
|
||||
*
|
||||
* @throws InterruptedException if the thread is interrupted before unblocking
|
||||
*/
|
||||
void await() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Block until all stopwatches that have been {@link #create() created} and {@link Stopwatch#start() started} are
|
||||
* stopped.
|
||||
*
|
||||
* @param timeout the maximum length of time that this method should block
|
||||
* @param unit the unit for the timeout; may not be null
|
||||
* @throws InterruptedException if the thread is interrupted before unblocking
|
||||
*/
|
||||
void await(long timeout, TimeUnit unit) throws InterruptedException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new set of stopwatches. The resulting object is threadsafe, and each {@link Stopwatch} created by
|
||||
* {@link StopwatchSet#create()} is also threadsafe.
|
||||
*
|
||||
* @return the stopwatches set; never null
|
||||
*/
|
||||
public static StopwatchSet multiple() {
|
||||
MultipleDurations durations = new MultipleDurations();
|
||||
VariableLatch latch = new VariableLatch(0);
|
||||
return new StopwatchSet() {
|
||||
@Override
|
||||
public Statistics statistics() {
|
||||
return durations.statistics();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stopwatch create() {
|
||||
return createWith(durations, latch::countUp, latch::countDown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void await() throws InterruptedException {
|
||||
latch.await();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void await(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
latch.await(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return statistics().toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new stopwatch that updates the given {@link BaseDurations duration}, and optionally has functions to
|
||||
* be called after the stopwatch is started and stopped.
|
||||
* <p>
|
||||
* The resulting stopwatch is threadsafe.
|
||||
* </p>
|
||||
*
|
||||
* @param duration the duration that should be updated; may not be null
|
||||
* @param uponStart the function that should be called when the stopwatch is successfully started (after not running); may be
|
||||
* null
|
||||
* @param uponStop the function that should be called when the stopwatch is successfully stopped (after it was running); may
|
||||
* be null
|
||||
* @return the new stopwatch
|
||||
*/
|
||||
protected static Stopwatch createWith(BaseDurations duration, Runnable uponStart, Runnable uponStop) {
|
||||
return new Stopwatch() {
|
||||
private final AtomicLong started = new AtomicLong(0L);
|
||||
|
||||
@Override
|
||||
public Stopwatch start() {
|
||||
started.getAndUpdate(existing -> {
|
||||
if (existing == 0L) {
|
||||
// Has not yet been started ...
|
||||
existing = System.nanoTime();
|
||||
if (uponStart != null) uponStart.run();
|
||||
}
|
||||
return existing;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stopwatch stop() {
|
||||
started.getAndUpdate(existing -> {
|
||||
if (existing != 0L) {
|
||||
// Is running but has not yet been stopped ...
|
||||
duration.add(Duration.ofNanos(System.nanoTime() - existing));
|
||||
if (uponStop != null) uponStop.run();
|
||||
return 0L;
|
||||
}
|
||||
return existing;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Durations durations() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return durations().toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the readable string representation of the supplied duration.
|
||||
*
|
||||
* @param duration the duration; may not be null
|
||||
* @return the string representation; never null
|
||||
*/
|
||||
protected static String asString(Duration duration) {
|
||||
return duration.toString().substring(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link Durations} implementations.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@ThreadSafe
|
||||
protected static abstract class BaseDurations implements Durations {
|
||||
public abstract void add(Duration duration);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return statistics().toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Durations} implementation that only remembers the most recently {@link #add(Duration) added} duration.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@ThreadSafe
|
||||
private static final class SingleDuration extends BaseDurations {
|
||||
private final AtomicReference<Statistics> stats = new AtomicReference<>();
|
||||
|
||||
@Override
|
||||
public Statistics statistics() {
|
||||
return stats.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Duration duration) {
|
||||
LongSummaryStatistics stats = new LongSummaryStatistics();
|
||||
if (duration != null) stats.accept(duration.toNanos());
|
||||
this.stats.set(createStatistics(stats));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Durations} implementation that accumulates all {@link #add(Duration) added} durations.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@ThreadSafe
|
||||
private static final class MultipleDurations extends BaseDurations {
|
||||
private final ConcurrentLinkedQueue<Duration> durations = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@Override
|
||||
public Statistics statistics() {
|
||||
return createStatistics(durations.stream().mapToLong(Duration::toNanos).summaryStatistics());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Duration duration) {
|
||||
durations.add(duration);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* Interface that allows a class to more efficiently generate a string that is to be added to other strings via a
|
||||
* {@link StringJoiner}.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Stringifiable {
|
||||
|
||||
/**
|
||||
* Used the supplied {@link StringJoiner} to record string representations of this object. The supplied joiner may concatenate
|
||||
* these strings together using custom delimiter strings.
|
||||
*
|
||||
* @param joiner the string joiner instance that will accumulate the
|
||||
*/
|
||||
void asString(StringJoiner joiner);
|
||||
|
||||
}
|
364
debezium-core/src/main/java/io/debezium/util/Strings.java
Normal file
364
debezium-core/src/main/java/io/debezium/util/Strings.java
Normal file
@ -0,0 +1,364 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import io.debezium.annotation.ThreadSafe;
|
||||
|
||||
/**
|
||||
* String-related utility methods.
|
||||
*
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
@ThreadSafe
|
||||
public final class Strings {
|
||||
|
||||
/**
|
||||
* Represents a predicate (boolean-valued function) of one character argument.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public static interface CharacterPredicate {
|
||||
/**
|
||||
* Evaluates this predicate on the given character argument.
|
||||
*
|
||||
* @param c the input argument
|
||||
* @return {@code true} if the input argument matches the predicate, or {@code false} otherwise
|
||||
*/
|
||||
boolean test(char c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two {@link CharSequence} instances.
|
||||
*
|
||||
* @param str1 the first character sequence; may be null
|
||||
* @param str2 the second character sequence; may be null
|
||||
* @return a negative integer if the first sequence is less than the second, zero if the sequence are equivalent (including if
|
||||
* both are null), or a positive integer if the first sequence is greater than the second
|
||||
*/
|
||||
public static int compareTo(CharSequence str1, CharSequence str2) {
|
||||
if (str1 == str2) return 0;
|
||||
if (str1 == null) return -1;
|
||||
if (str2 == null) return 1;
|
||||
return str1.toString().compareTo(str2.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim away any leading or trailing whitespace characters.
|
||||
* <p>
|
||||
* This is semantically equivalent to {@link String#trim()} but instead uses {@link #trim(String, CharacterPredicate)}.
|
||||
*
|
||||
* @param str the string to be trimmed; may not be null
|
||||
* @return the trimmed string; never null
|
||||
* @see #trim(String,CharacterPredicate)
|
||||
*/
|
||||
public static String trim(String str) {
|
||||
return trim(str, c -> c <= ' '); // same logic as String.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim away any leading or trailing characters that satisfy the supplied predicate
|
||||
*
|
||||
* @param str the string to be trimmed; may not be null
|
||||
* @param predicate the predicate function; may not be null
|
||||
* @return the trimmed string; never null
|
||||
* @see #trim(String)
|
||||
*/
|
||||
public static String trim(String str, CharacterPredicate predicate) {
|
||||
int len = str.length();
|
||||
if (len == 0) return str;
|
||||
int st = 0;
|
||||
while ((st < len) && predicate.test(str.charAt(st))) {
|
||||
st++;
|
||||
}
|
||||
while ((st < len) && predicate.test(str.charAt(len - 1))) {
|
||||
len--;
|
||||
}
|
||||
return ((st > 0) || (len < str.length())) ? str.substring(st, len) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new string containing the specified character repeated a specific number of times.
|
||||
*
|
||||
* @param charToRepeat the character to repeat
|
||||
* @param numberOfRepeats the number of times the character is to repeat in the result; must be greater than 0
|
||||
* @return the resulting string
|
||||
*/
|
||||
public static String createString( final char charToRepeat,
|
||||
int numberOfRepeats ) {
|
||||
assert numberOfRepeats >= 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < numberOfRepeats; ++i) {
|
||||
sb.append(charToRepeat);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of the string, padding with the supplied character if the supplied string is shorter than desired, or
|
||||
* truncating the string if it is longer than desired. Unlike {@link #justifyLeft(String, int, char)}, this method does not
|
||||
* remove leading and trailing whitespace.
|
||||
*
|
||||
* @param original the string for which the length is to be set; may not be null
|
||||
* @param length the desired length; must be positive
|
||||
* @param padChar the character to use for padding, if the supplied string is not long enough
|
||||
* @return the string of the desired length
|
||||
* @see #justifyLeft(String, int, char)
|
||||
*/
|
||||
public static String setLength( String original,
|
||||
int length,
|
||||
char padChar ) {
|
||||
return justifyLeft(original, length, padChar, false);
|
||||
}
|
||||
|
||||
public static enum Justify {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
CENTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Justify the contents of the string.
|
||||
*
|
||||
* @param justify the way in which the string is to be justified
|
||||
* @param str the string to be right justified; if null, an empty string is used
|
||||
* @param width the desired width of the string; must be positive
|
||||
* @param padWithChar the character to use for padding, if needed
|
||||
* @return the right justified string
|
||||
*/
|
||||
public static String justify( Justify justify,
|
||||
String str,
|
||||
final int width,
|
||||
char padWithChar ) {
|
||||
switch (justify) {
|
||||
case LEFT:
|
||||
return justifyLeft(str, width, padWithChar);
|
||||
case RIGHT:
|
||||
return justifyRight(str, width, padWithChar);
|
||||
case CENTER:
|
||||
return justifyCenter(str, width, padWithChar);
|
||||
}
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Right justify the contents of the string, ensuring that the string ends at the last character. If the supplied string is
|
||||
* longer than the desired width, the leading characters are removed so that the last character in the supplied string at the
|
||||
* last position. If the supplied string is shorter than the desired width, the padding character is inserted one or more
|
||||
* times such that the last character in the supplied string appears as the last character in the resulting string and that
|
||||
* the length matches that specified.
|
||||
*
|
||||
* @param str the string to be right justified; if null, an empty string is used
|
||||
* @param width the desired width of the string; must be positive
|
||||
* @param padWithChar the character to use for padding, if needed
|
||||
* @return the right justified string
|
||||
*/
|
||||
public static String justifyRight( String str,
|
||||
final int width,
|
||||
char padWithChar ) {
|
||||
assert width > 0;
|
||||
// Trim the leading and trailing whitespace ...
|
||||
str = str != null ? str.trim() : "";
|
||||
|
||||
final int length = str.length();
|
||||
int addChars = width - length;
|
||||
if (addChars < 0) {
|
||||
// truncate the first characters, keep the last
|
||||
return str.subSequence(length - width, length).toString();
|
||||
}
|
||||
// Prepend the whitespace ...
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
while (addChars > 0) {
|
||||
sb.append(padWithChar);
|
||||
--addChars;
|
||||
}
|
||||
|
||||
// Write the content ...
|
||||
sb.append(str);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Left justify the contents of the string, ensuring that the supplied string begins at the first character and that the
|
||||
* resulting string is of the desired length. If the supplied string is longer than the desired width, it is truncated to the
|
||||
* specified length. If the supplied string is shorter than the desired width, the padding character is added to the end of
|
||||
* the string one or more times such that the length is that specified. All leading and trailing whitespace is removed.
|
||||
*
|
||||
* @param str the string to be left justified; if null, an empty string is used
|
||||
* @param width the desired width of the string; must be positive
|
||||
* @param padWithChar the character to use for padding, if needed
|
||||
* @return the left justified string
|
||||
* @see #setLength(String, int, char)
|
||||
*/
|
||||
public static String justifyLeft( String str,
|
||||
final int width,
|
||||
char padWithChar ) {
|
||||
return justifyLeft(str, width, padWithChar, true);
|
||||
}
|
||||
|
||||
protected static String justifyLeft( String str,
|
||||
final int width,
|
||||
char padWithChar,
|
||||
boolean trimWhitespace ) {
|
||||
// Trim the leading and trailing whitespace ...
|
||||
str = str != null ? (trimWhitespace ? str.trim() : str) : "";
|
||||
|
||||
int addChars = width - str.length();
|
||||
if (addChars < 0) {
|
||||
// truncate
|
||||
return str.subSequence(0, width).toString();
|
||||
}
|
||||
// Write the content ...
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(str);
|
||||
|
||||
// Append the whitespace ...
|
||||
while (addChars > 0) {
|
||||
sb.append(padWithChar);
|
||||
--addChars;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Center the contents of the string. If the supplied string is longer than the desired width, it is truncated to the
|
||||
* specified length. If the supplied string is shorter than the desired width, padding characters are added to the beginning
|
||||
* and end of the string such that the length is that specified; one additional padding character is prepended if required.
|
||||
* All leading and trailing whitespace is removed before centering.
|
||||
*
|
||||
* @param str the string to be left justified; if null, an empty string is used
|
||||
* @param width the desired width of the string; must be positive
|
||||
* @param padWithChar the character to use for padding, if needed
|
||||
* @return the left justified string
|
||||
* @see #setLength(String, int, char)
|
||||
*/
|
||||
public static String justifyCenter( String str,
|
||||
final int width,
|
||||
char padWithChar ) {
|
||||
// Trim the leading and trailing whitespace ...
|
||||
str = str != null ? str.trim() : "";
|
||||
|
||||
int addChars = width - str.length();
|
||||
if (addChars < 0) {
|
||||
// truncate
|
||||
return str.subSequence(0, width).toString();
|
||||
}
|
||||
// Write the content ...
|
||||
int prependNumber = addChars / 2;
|
||||
int appendNumber = prependNumber;
|
||||
if ((prependNumber + appendNumber) != addChars) {
|
||||
++prependNumber;
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
// Prepend the pad character(s) ...
|
||||
while (prependNumber > 0) {
|
||||
sb.append(padWithChar);
|
||||
--prependNumber;
|
||||
}
|
||||
|
||||
// Add the actual content
|
||||
sb.append(str);
|
||||
|
||||
// Append the pad character(s) ...
|
||||
while (appendNumber > 0) {
|
||||
sb.append(padWithChar);
|
||||
--appendNumber;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stack trace of the supplied exception.
|
||||
*
|
||||
* @param throwable the exception for which the stack trace is to be returned
|
||||
* @return the stack trace, or null if the supplied exception is null
|
||||
*/
|
||||
public static String getStackTrace(Throwable throwable) {
|
||||
if (throwable == null) return null;
|
||||
final ByteArrayOutputStream bas = new ByteArrayOutputStream();
|
||||
final PrintWriter pw = new PrintWriter(bas);
|
||||
throwable.printStackTrace(pw);
|
||||
pw.close();
|
||||
return bas.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the supplied string as a integer value.
|
||||
*
|
||||
* @param value the string representation of a integer value
|
||||
* @param defaultValue the value to return if the string value is null or cannot be parsed as an int
|
||||
* @return the int value
|
||||
*/
|
||||
public static int asInt(String value, int defaultValue) {
|
||||
if (value != null) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the supplied string as a long value.
|
||||
*
|
||||
* @param value the string representation of a long value
|
||||
* @param defaultValue the value to return if the string value is null or cannot be parsed as a long
|
||||
* @return the long value
|
||||
*/
|
||||
public static long asLong(String value, long defaultValue) {
|
||||
if (value != null) {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the supplied string as a double value.
|
||||
*
|
||||
* @param value the string representation of a double value
|
||||
* @param defaultValue the value to return if the string value is null or cannot be parsed as a double
|
||||
* @return the double value
|
||||
*/
|
||||
public static double asDouble(String value, double defaultValue) {
|
||||
if (value != null) {
|
||||
try {
|
||||
return Double.parseDouble(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the supplied string as a boolean value.
|
||||
*
|
||||
* @param value the string representation of a boolean value
|
||||
* @param defaultValue the value to return if the string value is null or cannot be parsed as a boolean
|
||||
* @return the boolean value
|
||||
*/
|
||||
public static boolean asBoolean(String value, boolean defaultValue) {
|
||||
if (value != null) {
|
||||
try {
|
||||
return Boolean.parseBoolean(value);
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private Strings() {
|
||||
}
|
||||
}
|
234
debezium-core/src/main/java/io/debezium/util/VariableLatch.java
Normal file
234
debezium-core/src/main/java/io/debezium/util/VariableLatch.java
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Based on java.util.concurrent.CountDownLatch, which was
|
||||
* Written by Doug Lea with assistance from members of JCP JSR-166
|
||||
* Expert Group and released to the public domain, as explained at
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*
|
||||
* Any changes relative to CountDownLatch are also released to the
|
||||
* public domain, as explained at
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
package io.debezium.util;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
|
||||
|
||||
import io.debezium.annotation.ThreadSafe;
|
||||
|
||||
/**
|
||||
* A latch that works similarly to {@link CountDownLatch} except that it can also increase the count dynamically.
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class VariableLatch {
|
||||
|
||||
/**
|
||||
* Create a new variable latch.
|
||||
*
|
||||
* @return the variable latch; never null
|
||||
*/
|
||||
public static VariableLatch create() {
|
||||
return create(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new variable latch.
|
||||
*
|
||||
* @param initialValue the initial number of latches
|
||||
* @return the variable latch; never null
|
||||
*/
|
||||
public static VariableLatch create(int initialValue) {
|
||||
return new VariableLatch(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronization control For CountDownLatch.
|
||||
* Uses AQS state to represent count.
|
||||
*/
|
||||
private static final class Sync extends AbstractQueuedSynchronizer {
|
||||
private static final long serialVersionUID = 4982264981922014374L;
|
||||
|
||||
Sync(int count) {
|
||||
setState(count);
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int tryAcquireShared(int acquires) {
|
||||
return (getState() == 0) ? 1 : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean tryReleaseShared(int releases) {
|
||||
// Increment or decrement count; signal when transition to zero
|
||||
for (;;) {
|
||||
int c = getState();
|
||||
if (c == 0 && releases >= 0) return false;
|
||||
int nextc = c - releases;
|
||||
if (nextc < 0) nextc = 0;
|
||||
if (compareAndSetState(c, nextc))
|
||||
return nextc == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Sync sync;
|
||||
|
||||
/**
|
||||
* Constructs a {@code CountDownLatch} initialized with the given count.
|
||||
*
|
||||
* @param count the number of times {@link #countDown} must be invoked
|
||||
* before threads can pass through {@link #await}
|
||||
* @throws IllegalArgumentException if {@code count} is negative
|
||||
*/
|
||||
public VariableLatch(int count) {
|
||||
if (count < 0) throw new IllegalArgumentException("count < 0");
|
||||
this.sync = new Sync(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the current thread to wait until the latch has counted down to
|
||||
* zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
|
||||
*
|
||||
* <p>
|
||||
* If the current count is zero then this method returns immediately.
|
||||
*
|
||||
* <p>
|
||||
* If the current count is greater than zero then the current thread becomes disabled for thread scheduling purposes and lies
|
||||
* dormant until one of two things happen:
|
||||
* <ul>
|
||||
* <li>The count reaches zero due to invocations of the {@link #countDown} method; or
|
||||
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the current thread.
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* If the current thread:
|
||||
* <ul>
|
||||
* <li>has its interrupted status set on entry to this method; or
|
||||
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
|
||||
* </ul>
|
||||
* then {@link InterruptedException} is thrown and the current thread's interrupted status is cleared.
|
||||
*
|
||||
* @throws InterruptedException if the current thread is interrupted
|
||||
* while waiting
|
||||
*/
|
||||
public void await() throws InterruptedException {
|
||||
sync.acquireSharedInterruptibly(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the current thread to wait until the latch has counted down to
|
||||
* zero, unless the thread is {@linkplain Thread#interrupt interrupted},
|
||||
* or the specified waiting time elapses.
|
||||
*
|
||||
* <p>
|
||||
* If the current count is zero then this method returns immediately with the value {@code true}.
|
||||
*
|
||||
* <p>
|
||||
* If the current count is greater than zero then the current thread becomes disabled for thread scheduling purposes and lies
|
||||
* dormant until one of three things happen:
|
||||
* <ul>
|
||||
* <li>The count reaches zero due to invocations of the {@link #countDown} method; or
|
||||
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the current thread; or
|
||||
* <li>The specified waiting time elapses.
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* If the count reaches zero then the method returns with the value {@code true}.
|
||||
*
|
||||
* <p>
|
||||
* If the current thread:
|
||||
* <ul>
|
||||
* <li>has its interrupted status set on entry to this method; or
|
||||
* <li>is {@linkplain Thread#interrupt interrupted} while waiting,
|
||||
* </ul>
|
||||
* then {@link InterruptedException} is thrown and the current thread's interrupted status is cleared.
|
||||
*
|
||||
* <p>
|
||||
* If the specified waiting time elapses then the value {@code false} is returned. If the time is less than or equal to zero,
|
||||
* the method will not wait at all.
|
||||
*
|
||||
* @param timeout the maximum time to wait
|
||||
* @param unit the time unit of the {@code timeout} argument
|
||||
* @return {@code true} if the count reached zero and {@code false} if the waiting time elapsed before the count reached zero
|
||||
* @throws InterruptedException if the current thread is interrupted
|
||||
* while waiting
|
||||
*/
|
||||
public boolean await(long timeout, TimeUnit unit)
|
||||
throws InterruptedException {
|
||||
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the count of the latch, releasing all waiting threads if the count reaches zero.
|
||||
*
|
||||
* <p>
|
||||
* If the current count is greater than zero then it is decremented. If the new count is zero then all waiting threads are
|
||||
* re-enabled for thread scheduling purposes.
|
||||
*
|
||||
* <p>
|
||||
* If the current count equals zero then nothing happens.
|
||||
*/
|
||||
public void countDown() {
|
||||
sync.releaseShared(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the count of the latch, releasing all waiting threads if the count reaches zero.
|
||||
*
|
||||
* <p>
|
||||
* If the current count is greater than zero then it is decremented. If the new count is zero then all waiting threads are
|
||||
* re-enabled for thread scheduling purposes.
|
||||
*
|
||||
* <p>
|
||||
* If the current count equals zero then nothing happens.
|
||||
*
|
||||
* @param count the number of counts to decrease
|
||||
*/
|
||||
public void countDown(int count) {
|
||||
sync.releaseShared(1 * (Math.abs(count)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the count of the latch by one.
|
||||
*/
|
||||
public void countUp() {
|
||||
sync.releaseShared(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the count of the latch by a positive number.
|
||||
*
|
||||
* @param count the number of counts to increase
|
||||
*/
|
||||
public void countUp(int count) {
|
||||
sync.releaseShared(-1 * (Math.abs(count)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current count.
|
||||
*
|
||||
* <p>
|
||||
* This method is typically used for debugging and testing purposes.
|
||||
*
|
||||
* @return the current count
|
||||
*/
|
||||
public long getCount() {
|
||||
return sync.getCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string identifying this latch, as well as its state.
|
||||
* The state, in brackets, includes the String {@code "Count ="} followed by the current count.
|
||||
*
|
||||
* @return a string identifying this latch, as well as its state
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "[Count = " + sync.getCount() + "]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.jdbc;
|
||||
|
||||
import io.debezium.config.Configuration;
|
||||
|
||||
public class TestDatabase {
|
||||
|
||||
public static JdbcConfiguration testConfig( String databaseName ) {
|
||||
return buildTestConfig().withDatabase(databaseName).build();
|
||||
}
|
||||
|
||||
public static JdbcConfiguration.Builder buildTestConfig() {
|
||||
return JdbcConfiguration.copy(Configuration.fromSystemProperties("database."));
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.injest.jdbc.util;
|
||||
|
@ -19,7 +19,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-ingest-jdbc</artifactId>
|
||||
<artifactId>debezium-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
@ -36,7 +36,7 @@
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-ingest-jdbc</artifactId>
|
||||
<artifactId>debezium-core</artifactId>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -63,7 +63,7 @@
|
||||
work on all platforms. We'll set some of these as system properties during integration testing.
|
||||
-->
|
||||
<database.port>3306</database.port>
|
||||
<database.username>mysqluser</database.username>
|
||||
<database.user>mysqluser</database.user>
|
||||
<database.password>mysqlpw</database.password>
|
||||
</properties>
|
||||
<build>
|
||||
@ -97,7 +97,7 @@
|
||||
<env>
|
||||
<MYSQL_ROOT_PASSWORD>debezium-rocks</MYSQL_ROOT_PASSWORD>
|
||||
<MYSQL_DATABASE>mysql</MYSQL_DATABASE> <!-- database created upon init -->
|
||||
<MYSQL_USER>${database.username}</MYSQL_USER>
|
||||
<MYSQL_USER>${database.user}</MYSQL_USER>
|
||||
<MYSQL_PASSWORD>${database.password}</MYSQL_PASSWORD>
|
||||
</env>
|
||||
<ports>
|
||||
@ -165,7 +165,7 @@
|
||||
<!-- Make these available to the tests via system properties -->
|
||||
<database.hostname>${dockerhost.ip}</database.hostname>
|
||||
<database.port>${database.port}</database.port>
|
||||
<database.username>${database.username}</database.username>
|
||||
<database.user>${database.user}</database.user>
|
||||
<database.password>${database.password}</database.password>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
@ -211,6 +211,6 @@
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</testResource>
|
||||
</testResources>
|
||||
</testResources>
|
||||
</build>
|
||||
</project></project>
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.ingest.mysql;
|
||||
|
||||
import io.debezium.config.Configuration;
|
||||
import io.debezium.jdbc.JdbcConnection;
|
||||
|
||||
/**
|
||||
* A utility for working with MySQL connections.
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
public class MySQLConnection extends JdbcConnection {
|
||||
|
||||
protected static ConnectionFactory FACTORY = JdbcConnection.patternBasedFactory("jdbc:mysql://${hostname}:${port}/${dbname}");
|
||||
|
||||
/**
|
||||
* Create a new instance with the given configuration and connection factory.
|
||||
*
|
||||
* @param config the configuration; may not be null
|
||||
*/
|
||||
public MySQLConnection(Configuration config) {
|
||||
super(config, FACTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the given configuration and connection factory, and specify the operations that should be
|
||||
* run against each newly-established connection.
|
||||
*
|
||||
* @param config the configuration; may not be null
|
||||
* @param initialOperations the initial operations that should be run on each new connection; may be null
|
||||
*/
|
||||
public MySQLConnection(Configuration config, Operations initialOperations) {
|
||||
super(config, FACTORY, initialOperations);
|
||||
}
|
||||
}
|
10
debezium-ingest-mysql/src/test/docker/init/readbinlog.sql
Normal file
10
debezium-ingest-mysql/src/test/docker/init/readbinlog.sql
Normal file
@ -0,0 +1,10 @@
|
||||
# In production you would almost certainly limit the replication user must be on the follower (slave) machine,
|
||||
# to prevent other clients accessing the log from other machines. For example, 'replicator'@'follower.acme.com'.
|
||||
#
|
||||
# However, this grant is equivalent to specifying *any* hosts, which makes this easier since the docker host
|
||||
# is not easily known to the Docker container
|
||||
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replicator' IDENTIFIED BY 'replpass';
|
||||
|
||||
# Create the database that we'll use to populate data and watch the effect in the binlog
|
||||
CREATE DATABASE readbinlog_test;
|
||||
GRANT ALL PRIVILEGES ON readbinlog_test.* TO 'mysqluser'@'%';
|
@ -1,28 +1,29 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.ingest.mysql;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import io.debezium.injest.jdbc.util.TestDatabase;
|
||||
import io.debezium.jdbc.TestDatabase;
|
||||
|
||||
public class ConnectionIT {
|
||||
|
||||
@Test
|
||||
public void shouldConnectToDefaulDatabase() throws SQLException {
|
||||
try (Connection conn = TestDatabase.connect("jdbc:mysql://${hostname}:${port}/mysql");) {
|
||||
try (MySQLConnection conn = new MySQLConnection( TestDatabase.testConfig("mysql") );) {
|
||||
conn.connect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConnectToEmptyDatabase() throws SQLException {
|
||||
try (Connection conn = TestDatabase.connect("jdbc:mysql://${hostname}:${port}/emptydb");) {
|
||||
try (MySQLConnection conn = new MySQLConnection( TestDatabase.testConfig("emptydb") );) {
|
||||
conn.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,468 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.ingest.mysql;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.github.shyiko.mysql.binlog.BinaryLogClient;
|
||||
import com.github.shyiko.mysql.binlog.BinaryLogClient.EventListener;
|
||||
import com.github.shyiko.mysql.binlog.BinaryLogClient.LifecycleListener;
|
||||
import com.github.shyiko.mysql.binlog.event.DeleteRowsEventData;
|
||||
import com.github.shyiko.mysql.binlog.event.Event;
|
||||
import com.github.shyiko.mysql.binlog.event.EventData;
|
||||
import com.github.shyiko.mysql.binlog.event.EventType;
|
||||
import com.github.shyiko.mysql.binlog.event.QueryEventData;
|
||||
import com.github.shyiko.mysql.binlog.event.TableMapEventData;
|
||||
import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData;
|
||||
import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
|
||||
import com.github.shyiko.mysql.binlog.event.XidEventData;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import io.debezium.jdbc.JdbcConfiguration;
|
||||
import io.debezium.jdbc.TestDatabase;
|
||||
|
||||
public class ReadBinLogIT {
|
||||
|
||||
protected static final Logger LOGGER = LoggerFactory.getLogger(ReadBinLogIT.class);
|
||||
protected static final long DEFAULT_TIMEOUT = TimeUnit.SECONDS.toMillis(3);
|
||||
|
||||
private JdbcConfiguration config;
|
||||
private EventCounters counters;
|
||||
private BinaryLogClient client;
|
||||
private MySQLConnection conn;
|
||||
private List<Event> events = new ArrayList<>();
|
||||
|
||||
@Before
|
||||
public void beforeEach() throws TimeoutException, IOException, SQLException, InterruptedException {
|
||||
events.clear();
|
||||
|
||||
config = TestDatabase.buildTestConfig().withDatabase("readbinlog_test").build();
|
||||
|
||||
// Connect the normal SQL client ...
|
||||
conn = new MySQLConnection(config);
|
||||
conn.connect();
|
||||
|
||||
// Connect the bin log client ...
|
||||
counters = new EventCounters();
|
||||
client = new BinaryLogClient(config.getHostname(), config.getPort(), "replicator", "replpass");
|
||||
client.setServerId(client.getServerId() - 1); // avoid clashes between BinaryLogClient instances
|
||||
client.setKeepAlive(false);
|
||||
client.registerEventListener(this::logEvent);
|
||||
client.registerEventListener(counters);
|
||||
client.registerEventListener(this::recordEvent);
|
||||
client.registerLifecycleListener(new TraceLifecycleListener());
|
||||
client.connect(DEFAULT_TIMEOUT); // does not block
|
||||
|
||||
// Set up the table as one transaction and wait to see the events ...
|
||||
conn.execute("DROP TABLE IF EXISTS person",
|
||||
"CREATE TABLE person (name VARCHAR(255) primary key)");
|
||||
counters.waitFor(2, EventType.QUERY, DEFAULT_TIMEOUT);
|
||||
counters.reset();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterEach() throws IOException, SQLException {
|
||||
events.clear();
|
||||
try {
|
||||
if (client != null) client.disconnect();
|
||||
} finally {
|
||||
client = null;
|
||||
try {
|
||||
if (conn != null) conn.close();
|
||||
} finally {
|
||||
conn = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCaptureSingleWriteUpdateDeleteEvents() throws Exception {
|
||||
// write/insert
|
||||
conn.execute("INSERT INTO person VALUES ('Georgia')");
|
||||
counters.waitFor(1, WriteRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
List<WriteRowsEventData> writeRowEvents = recordedEventData(WriteRowsEventData.class, 1);
|
||||
assertRows(writeRowEvents.get(0), rows().insertedRow("Georgia"));
|
||||
|
||||
// update
|
||||
conn.execute("UPDATE person SET name = 'Maggie' WHERE name = 'Georgia'");
|
||||
counters.waitFor(1, UpdateRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
List<UpdateRowsEventData> updateRowEvents = recordedEventData(UpdateRowsEventData.class, 1);
|
||||
assertRows(updateRowEvents.get(0), rows().changeRow("Georgia").to("Maggie"));
|
||||
|
||||
// delete
|
||||
conn.execute("DELETE FROM person WHERE name = 'Maggie'");
|
||||
counters.waitFor(1, DeleteRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
List<DeleteRowsEventData> deleteRowEvents = recordedEventData(DeleteRowsEventData.class, 1);
|
||||
assertRows(deleteRowEvents.get(0), rows().removedRow("Maggie"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCaptureMultipleWriteUpdateDeleteEvents() throws Exception {
|
||||
// write/insert as a single transaction
|
||||
conn.execute("INSERT INTO person VALUES ('Georgia')",
|
||||
"INSERT INTO person VALUES ('Janice')");
|
||||
counters.waitFor(1, QueryEventData.class, DEFAULT_TIMEOUT); // BEGIN
|
||||
counters.waitFor(1, TableMapEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(2, WriteRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, XidEventData.class, DEFAULT_TIMEOUT); // COMMIT
|
||||
List<WriteRowsEventData> writeRowEvents = recordedEventData(WriteRowsEventData.class, 2);
|
||||
assertRows(writeRowEvents.get(0), rows().insertedRow("Georgia"));
|
||||
assertRows(writeRowEvents.get(1), rows().insertedRow("Janice"));
|
||||
counters.reset();
|
||||
|
||||
// update as a single transaction
|
||||
conn.execute("UPDATE person SET name = 'Maggie' WHERE name = 'Georgia'",
|
||||
"UPDATE person SET name = 'Jamie' WHERE name = 'Janice'");
|
||||
counters.waitFor(1, QueryEventData.class, DEFAULT_TIMEOUT); // BEGIN
|
||||
counters.waitFor(1, TableMapEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(2, UpdateRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, XidEventData.class, DEFAULT_TIMEOUT); // COMMIT
|
||||
List<UpdateRowsEventData> updateRowEvents = recordedEventData(UpdateRowsEventData.class, 2);
|
||||
assertRows(updateRowEvents.get(0), rows().changeRow("Georgia").to("Maggie"));
|
||||
assertRows(updateRowEvents.get(1), rows().changeRow("Janice").to("Jamie"));
|
||||
counters.reset();
|
||||
|
||||
// delete as a single transaction
|
||||
conn.execute("DELETE FROM person WHERE name = 'Maggie'",
|
||||
"DELETE FROM person WHERE name = 'Jamie'");
|
||||
counters.waitFor(1, QueryEventData.class, DEFAULT_TIMEOUT); // BEGIN
|
||||
counters.waitFor(1, TableMapEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(2, DeleteRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, XidEventData.class, DEFAULT_TIMEOUT); // COMMIT
|
||||
List<DeleteRowsEventData> deleteRowEvents = recordedEventData(DeleteRowsEventData.class, 2);
|
||||
assertRows(deleteRowEvents.get(0), rows().removedRow("Maggie"));
|
||||
assertRows(deleteRowEvents.get(1), rows().removedRow("Jamie"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCaptureMultipleWriteUpdateDeletesInSingleEvents() throws Exception {
|
||||
// write/insert as a single statement/transaction
|
||||
conn.execute("INSERT INTO person VALUES ('Georgia'),('Janice')");
|
||||
counters.waitFor(1, QueryEventData.class, DEFAULT_TIMEOUT); // BEGIN
|
||||
counters.waitFor(1, TableMapEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, WriteRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, XidEventData.class, DEFAULT_TIMEOUT); // COMMIT
|
||||
List<WriteRowsEventData> writeRowEvents = recordedEventData(WriteRowsEventData.class, 1);
|
||||
assertRows(writeRowEvents.get(0), rows().insertedRow("Georgia").insertedRow("Janice"));
|
||||
counters.reset();
|
||||
|
||||
// update as a single statement/transaction
|
||||
conn.execute("UPDATE person SET name = CASE " +
|
||||
" WHEN name = 'Georgia' THEN 'Maggie' " +
|
||||
" WHEN name = 'Janice' THEN 'Jamie' " +
|
||||
" END " +
|
||||
"WHERE name IN ('Georgia','Janice')");
|
||||
counters.waitFor(1, QueryEventData.class, DEFAULT_TIMEOUT); // BEGIN
|
||||
counters.waitFor(1, TableMapEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, UpdateRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, XidEventData.class, DEFAULT_TIMEOUT); // COMMIT
|
||||
List<UpdateRowsEventData> updateRowEvents = recordedEventData(UpdateRowsEventData.class, 1);
|
||||
assertRows(updateRowEvents.get(0), rows().changeRow("Georgia").to("Maggie").changeRow("Janice").to("Jamie"));
|
||||
counters.reset();
|
||||
|
||||
// delete as a single statement/transaction
|
||||
conn.execute("DELETE FROM person WHERE name IN ('Maggie','Jamie')");
|
||||
counters.waitFor(1, QueryEventData.class, DEFAULT_TIMEOUT); // BEGIN
|
||||
counters.waitFor(1, TableMapEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, DeleteRowsEventData.class, DEFAULT_TIMEOUT);
|
||||
counters.waitFor(1, XidEventData.class, DEFAULT_TIMEOUT); // COMMIT
|
||||
List<DeleteRowsEventData> deleteRowEvents = recordedEventData(DeleteRowsEventData.class, 1);
|
||||
assertRows(deleteRowEvents.get(0), rows().removedRow("Maggie").removedRow("Jamie"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldQueryInformationSchema() throws Exception {
|
||||
// long tableId = writeRows.getTableId();
|
||||
// BitSet columnIds = writeRows.getIncludedColumns();
|
||||
//
|
||||
// conn.query("select TABLE_NAME, ROW_FORMAT, TABLE_ROWS, AVG_ROW_LENGTH, DATA_LENGTH, MAX_DATA_LENGTH, INDEX_LENGTH, DATA_FREE, " +
|
||||
// "AUTO_INCREMENT, CREATE_TIME, UPDATE_TIME, CHECK_TIME, TABLE_COLLATION, CHECKSUM, CREATE_OPTIONS, TABLE_COMMENT " +
|
||||
// "from INFORMATION_SCHEMA.TABLES " +
|
||||
// "where TABLE_SCHEMA like 'readbinlog_test' and TABLE_NAME like 'person'", conn::print);
|
||||
// conn.query("select TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, " +
|
||||
// "DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE, " +
|
||||
// "CHARACTER_SET_NAME, COLLATION_NAME from INFORMATION_SCHEMA.COLUMNS " +
|
||||
// "where TABLE_SCHEMA like 'readbinlog_test' and TABLE_NAME like 'person'", conn::print);
|
||||
|
||||
}
|
||||
|
||||
protected void logEvent(Event event) {
|
||||
LOGGER.info("Received event: " + event);
|
||||
}
|
||||
|
||||
protected void recordEvent(Event event) {
|
||||
synchronized (events) {
|
||||
events.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
protected <T extends EventData> List<T> recordedEventData(Class<T> eventType, int expectedCount) {
|
||||
List<T> results = null;
|
||||
synchronized (events) {
|
||||
results = events.stream().map(Event::getData).filter(eventType::isInstance).map(eventType::cast).collect(Collectors.toList());
|
||||
}
|
||||
if (expectedCount > -1) {
|
||||
assertThat(results.size()).isEqualTo(expectedCount);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
protected void assertRow(Serializable[] data, Serializable... expected) {
|
||||
assertThat(data.length).isEqualTo(expected.length);
|
||||
assertThat(data).contains((Object[]) expected);
|
||||
}
|
||||
|
||||
protected void assertRows( WriteRowsEventData eventData, int numRowsInEvent, Serializable... expectedValuesInRows ) {
|
||||
assertThat(eventData.getRows().size()).isEqualTo(numRowsInEvent);
|
||||
int valuePosition = 0;
|
||||
for (Serializable[] row : eventData.getRows() ) {
|
||||
for ( Serializable value : row ) {
|
||||
assertThat(value).isEqualTo(expectedValuesInRows[valuePosition++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Row {
|
||||
public Serializable[] fromValues;
|
||||
public Serializable[] toValues;
|
||||
|
||||
}
|
||||
|
||||
public static interface UpdateBuilder {
|
||||
RowBuilder to( Serializable...values );
|
||||
}
|
||||
|
||||
public static class RowBuilder {
|
||||
private List<Row> rows = new ArrayList<>();
|
||||
private Row nextRow = null;
|
||||
public RowBuilder insertedRow( Serializable...values ) {
|
||||
maybeAddRow();
|
||||
return changeRow().to(values);
|
||||
}
|
||||
public RowBuilder removedRow( Serializable...values ) {
|
||||
maybeAddRow();
|
||||
return changeRow(values).to(values);
|
||||
}
|
||||
public UpdateBuilder changeRow( Serializable...values ) {
|
||||
maybeAddRow();
|
||||
nextRow = new Row();
|
||||
nextRow.fromValues = values;
|
||||
return new UpdateBuilder() {
|
||||
@Override
|
||||
public RowBuilder to(Serializable... values) {
|
||||
nextRow.toValues = values;
|
||||
return RowBuilder.this;
|
||||
}
|
||||
};
|
||||
}
|
||||
protected void maybeAddRow() {
|
||||
if ( nextRow != null ) {
|
||||
rows.add(nextRow);
|
||||
nextRow = null;
|
||||
}
|
||||
}
|
||||
protected List<Row> rows() {
|
||||
maybeAddRow();
|
||||
return rows;
|
||||
}
|
||||
protected boolean findInsertedRow( Serializable[] values ) {
|
||||
maybeAddRow();
|
||||
for ( Iterator<Row> iter = rows.iterator(); iter.hasNext(); ) {
|
||||
Row expectedRow = iter.next();
|
||||
if ( Arrays.deepEquals(expectedRow.toValues,values)) {
|
||||
iter.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected boolean findDeletedRow( Serializable[] values ) {
|
||||
maybeAddRow();
|
||||
for ( Iterator<Row> iter = rows.iterator(); iter.hasNext(); ) {
|
||||
Row expectedRow = iter.next();
|
||||
if ( Arrays.deepEquals(expectedRow.fromValues,values)) {
|
||||
iter.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected boolean findUpdatedRow( Serializable[] oldValues, Serializable[] newValues ) {
|
||||
maybeAddRow();
|
||||
for ( Iterator<Row> iter = rows.iterator(); iter.hasNext(); ) {
|
||||
Row expectedRow = iter.next();
|
||||
if ( Arrays.deepEquals(expectedRow.fromValues,oldValues) && Arrays.deepEquals(expectedRow.toValues,newValues)) {
|
||||
iter.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected RowBuilder rows() {
|
||||
return new RowBuilder();
|
||||
}
|
||||
|
||||
protected void assertRows( UpdateRowsEventData eventData, RowBuilder rows ) {
|
||||
assertThat(eventData.getRows().size()).isEqualTo(rows.rows().size());
|
||||
for (Map.Entry<Serializable[], Serializable[]> row : eventData.getRows() ) {
|
||||
if ( !rows.findUpdatedRow(row.getKey(), row.getValue()) ) {
|
||||
fail("Failed to find updated row: " + eventData );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertRows( WriteRowsEventData eventData, RowBuilder rows ) {
|
||||
assertThat(eventData.getRows().size()).isEqualTo(rows.rows().size());
|
||||
for (Serializable[] removedRow : eventData.getRows() ) {
|
||||
if ( !rows.findInsertedRow(removedRow) ) {
|
||||
fail("Failed to find inserted row: " + eventData );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertRows( DeleteRowsEventData eventData, RowBuilder rows ) {
|
||||
assertThat(eventData.getRows().size()).isEqualTo(rows.rows().size());
|
||||
for (Serializable[] removedRow : eventData.getRows() ) {
|
||||
if ( !rows.findDeletedRow(removedRow) ) {
|
||||
fail("Failed to find removed row: " + eventData );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static class EventCounters implements EventListener {
|
||||
/*
|
||||
* VariableLatch instances count down when receiving an event, and thus are negative. When callers wait for a specified
|
||||
* number of events to occur, the latch's count is incremented by the expected count. If the latch's resulting count is
|
||||
* less than or equal to 0, then the caller does not wait.
|
||||
*/
|
||||
private final ConcurrentMap<EventType, AtomicInteger> counterByType = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Class<? extends EventData>, AtomicInteger> counterByDataClass = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
counterByType.compute(event.getHeader().getEventType(), this::increment);
|
||||
EventData data = event.getData();
|
||||
if (data != null) {
|
||||
counterByDataClass.compute(data.getClass(), this::increment);
|
||||
}
|
||||
}
|
||||
|
||||
protected <K> AtomicInteger increment(K key, AtomicInteger counter) {
|
||||
if (counter == null) return new AtomicInteger(1);
|
||||
synchronized (counter) {
|
||||
counter.incrementAndGet();
|
||||
counter.notify();
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until the listener has seen the specified number of events with the given type.
|
||||
*
|
||||
* @param eventCount the number of events
|
||||
* @param type the type of event
|
||||
* @param timeoutMillis the maximum amount of time in milliseconds that this method should block
|
||||
* @throws InterruptedException if the thread was interrupted while waiting
|
||||
* @throws TimeoutException if the waiting timed out before the expected number of events were received
|
||||
*/
|
||||
public void waitFor(int eventCount, EventType type, long timeoutMillis) throws InterruptedException, TimeoutException {
|
||||
waitFor(type.name(), () -> counterByType.get(type), eventCount, timeoutMillis);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until the listener has seen the specified number of events with the given type.
|
||||
*
|
||||
* @param eventCount the number of events
|
||||
* @param eventDataClass the EventData subclass
|
||||
* @param timeoutMillis the maximum amount of time in milliseconds that this method should block
|
||||
* @throws InterruptedException if the thread was interrupted while waiting
|
||||
* @throws TimeoutException if the waiting timed out before the expected number of events were received
|
||||
*/
|
||||
public void waitFor(int eventCount, Class<? extends EventData> eventDataClass, long timeoutMillis)
|
||||
throws InterruptedException, TimeoutException {
|
||||
waitFor(eventDataClass.getSimpleName(), () -> counterByDataClass.get(eventDataClass), eventCount, timeoutMillis);
|
||||
}
|
||||
|
||||
private void waitFor(String eventTypeName, Supplier<AtomicInteger> counterGetter, int eventCount, long timeoutMillis)
|
||||
throws InterruptedException, TimeoutException {
|
||||
// Get the counter, and prepare for it to be null ...
|
||||
AtomicInteger counter = null;
|
||||
long stopTime = System.currentTimeMillis() + timeoutMillis;
|
||||
do {
|
||||
counter = counterGetter.get();
|
||||
} while (counter == null && System.currentTimeMillis() <= stopTime);
|
||||
if (counter == null) {
|
||||
// Did not even find a counter in this timeframe ...
|
||||
throw new TimeoutException("Timed out while waiting for " + eventCount + " " + eventTypeName + " events");
|
||||
}
|
||||
synchronized (counter) {
|
||||
counter.addAndGet(-eventCount);
|
||||
if (counter.get() != 0) {
|
||||
counter.wait(timeoutMillis);
|
||||
if (counter.get() != 0) {
|
||||
throw new TimeoutException("Timed out while waiting for " + eventCount + " " + eventTypeName + " events");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all counters.
|
||||
*/
|
||||
public void reset() {
|
||||
counterByDataClass.clear();
|
||||
counterByType.clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class TraceLifecycleListener implements LifecycleListener {
|
||||
|
||||
@Override
|
||||
public void onDisconnect(BinaryLogClient client) {
|
||||
LOGGER.info("Client disconnected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnect(BinaryLogClient client) {
|
||||
LOGGER.info("Client connected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommunicationFailure(BinaryLogClient client, Exception ex) {
|
||||
LOGGER.error("Client communication failure", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventDeserializationFailure(BinaryLogClient client, Exception ex) {
|
||||
LOGGER.error("Client received event deserialization failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-ingest-jdbc</artifactId>
|
||||
<artifactId>debezium-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
@ -32,7 +32,7 @@
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-ingest-jdbc</artifactId>
|
||||
<artifactId>debezium-core</artifactId>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -59,7 +59,7 @@
|
||||
work on all platforms. We'll set some of these as system properties during integration testing.
|
||||
-->
|
||||
<database.port>5432</database.port>
|
||||
<database.username>postgres</database.username>
|
||||
<database.user>postgres</database.user>
|
||||
<database.password>postgres</database.password>
|
||||
</properties>
|
||||
<build>
|
||||
@ -91,7 +91,7 @@
|
||||
</bind>
|
||||
</volumes-->
|
||||
<env>
|
||||
<POSTGRES_USER>${database.username}</POSTGRES_USER>
|
||||
<POSTGRES_USER>${database.user}</POSTGRES_USER>
|
||||
<POSTGRES_PASSWORD>${database.password}</POSTGRES_PASSWORD>
|
||||
</env>
|
||||
<ports>
|
||||
@ -159,7 +159,7 @@
|
||||
<!-- Make these available to the tests via system properties -->
|
||||
<database.hostname>${dockerhost.ip}</database.hostname>
|
||||
<database.port>${database.port}</database.port>
|
||||
<database.username>${database.username}</database.username>
|
||||
<database.user>${database.user}</database.user>
|
||||
<database.password>${database.password}</database.password>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.ingest.postgres;
|
||||
|
||||
import io.debezium.config.Configuration;
|
||||
import io.debezium.jdbc.JdbcConnection;
|
||||
|
||||
/**
|
||||
* A utility for working with MySQL connections.
|
||||
* @author Randall Hauch
|
||||
*/
|
||||
public class PostgresConnection extends JdbcConnection {
|
||||
|
||||
protected static ConnectionFactory FACTORY = JdbcConnection.patternBasedFactory("jdbc:postgresql://${hostname}:${port}/${dbname}");
|
||||
|
||||
/**
|
||||
* Create a new instance with the given configuration and connection factory.
|
||||
*
|
||||
* @param config the configuration; may not be null
|
||||
*/
|
||||
public PostgresConnection(Configuration config) {
|
||||
super(config, FACTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the given configuration and connection factory, and specify the operations that should be
|
||||
* run against each newly-established connection.
|
||||
*
|
||||
* @param config the configuration; may not be null
|
||||
* @param initialOperations the initial operations that should be run on each new connection; may be null
|
||||
*/
|
||||
public PostgresConnection(Configuration config, Operations initialOperations) {
|
||||
super(config, FACTORY, initialOperations);
|
||||
}
|
||||
}
|
@ -1,28 +1,30 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.ingest.postgresql;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import io.debezium.injest.jdbc.util.TestDatabase;
|
||||
import io.debezium.ingest.postgres.PostgresConnection;
|
||||
import io.debezium.jdbc.TestDatabase;
|
||||
|
||||
public class ConnectionIT {
|
||||
|
||||
@Test
|
||||
public void shouldConnectToDefaultDatabase() throws SQLException {
|
||||
try (Connection conn = TestDatabase.connect("jdbc:postgresql://${hostname}:${port}/postgres");) {
|
||||
public void shouldConnectToDefaulDatabase() throws SQLException {
|
||||
try (PostgresConnection conn = new PostgresConnection( TestDatabase.testConfig("postgres") );) {
|
||||
conn.connect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConnectToEmptyDatabase() throws SQLException {
|
||||
try (Connection conn = TestDatabase.connect("jdbc:postgresql://${hostname}:${port}/emptydb");) {
|
||||
try (PostgresConnection conn = new PostgresConnection( TestDatabase.testConfig("emptydb") );) {
|
||||
conn.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
pom.xml
13
pom.xml
@ -81,6 +81,7 @@
|
||||
</properties>
|
||||
<modules>
|
||||
<module>support/checkstyle</module>
|
||||
<module>debezium-core</module>
|
||||
<module>debezium-ingest-jdbc</module>
|
||||
<module>debezium-ingest-postgres</module>
|
||||
<module>debezium-ingest-mysql</module>
|
||||
@ -147,6 +148,11 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Debezium artifacts -->
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-ingest-jdbc</artifactId>
|
||||
@ -169,6 +175,13 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Debezium test artifacts -->
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.debezium</groupId>
|
||||
<artifactId>debezium-ingest-jdbc</artifactId>
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.checkstyle;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.checkstyle;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.checkstyle;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.checkstyle;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package io.debezium.checkstyle;
|
||||
|
@ -14,9 +14,9 @@
|
||||
<!--property name="fileExtensions" value="java,cnd"/-->
|
||||
<property name="fileExtensions" value="java"/>
|
||||
<!-- Exclude any classes that keep their own copyright and header -->
|
||||
<property name="excludedClasses" value=""/>
|
||||
<property name="excludedClasses" value="io.debezium.util.VariableLatch"/>
|
||||
<!-- Exclude any directories that contain completely different headers -->
|
||||
<!-- property name="excludedFilesRegex" value="io/debezium/something"/-->
|
||||
<property name="excludedFilesRegex" value="io/debezium/annotation/.*"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
@ -103,7 +103,7 @@
|
||||
<!-- Required to get SuppressionCommentFilter to work -->
|
||||
<module name="FileContentsHolder" />
|
||||
|
||||
<!-- Checks that ModeShape is expressly NOT using -->
|
||||
<!-- Checks that Debezium is expressly NOT using -->
|
||||
<!--module name="ModifiedControlVariable" /-->
|
||||
<!--module name="DeclarationOrder" /-->
|
||||
<!--module name="InnerTypeLast" /-->
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
@ -6,10 +6,10 @@
|
||||
enabled="true"
|
||||
id="org.eclipse.jdt.ui.text.codetemplates.filecomment"
|
||||
name="filecomment">
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
/*
|
||||
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
</template>
|
||||
</templates>
|
Loading…
Reference in New Issue
Block a user