Fix Docs typos
This commit is contained in:
@@ -19,7 +19,7 @@ See <<serdes>> for more information.
|
||||
|
||||
==== Container Stopping Error Handlers
|
||||
|
||||
Container error handlers are now provided for both record and batch listeners that treat any exceptions thrown by the listener as fatal/
|
||||
Container error handlers are now provided for both record and batch listeners that treat any exceptions thrown by the listener as fatal.
|
||||
They stop the container.
|
||||
See <<annotation-error-handling>> for more information.
|
||||
|
||||
|
||||
@@ -156,8 +156,7 @@ You can also configure the template by using standard `<bean/>` definitions.
|
||||
|
||||
Then, to use the template, you can invoke one of its methods.
|
||||
|
||||
When you use the methods with a `Message<?>` parameter, the topic, partition, and key information is provided in a message
|
||||
header that includes the following items:
|
||||
When you use the methods with a `Message<?>` parameter, the topic, partition, and key information is provided in a message header that includes the following items:
|
||||
|
||||
* `KafkaHeaders.TOPIC`
|
||||
* `KafkaHeaders.PARTITION_ID`
|
||||
@@ -166,8 +165,7 @@ header that includes the following items:
|
||||
|
||||
The message payload is the data.
|
||||
|
||||
Optionally, you can configure the `KafkaTemplate` with a `ProducerListener` to get an asynchronous callback with the
|
||||
results of the send (success or failure) instead of waiting for the `Future` to complete.
|
||||
Optionally, you can configure the `KafkaTemplate` with a `ProducerListener` to get an asynchronous callback with the results of the send (success or failure) instead of waiting for the `Future` to complete.
|
||||
The following listing shows the definition of the `ProducerListener` interface:
|
||||
|
||||
====
|
||||
@@ -185,18 +183,16 @@ public interface ProducerListener<K, V> {
|
||||
----
|
||||
====
|
||||
|
||||
By default, the template is configured with a `LoggingProducerListener`, which logs errors and does nothing when the
|
||||
send is successful.
|
||||
By default, the template is configured with a `LoggingProducerListener`, which logs errors and does nothing when the send is successful.
|
||||
|
||||
`onSuccess` is called only if `isInterestedInSuccess` returns `true`.
|
||||
|
||||
For convenience, the abstract `ProducerListenerAdapter` is provided in case you want to implement only one of the
|
||||
methods.
|
||||
For convenience, the abstract `ProducerListenerAdapter` is provided in case you want to implement only one of the methods.
|
||||
It returns `false` for `isInterestedInSuccess`.
|
||||
|
||||
Notice that the send methods return a `ListenableFuture<SendResult>`.
|
||||
You can register a callback with the listener to receive the result of the send asynchronously.
|
||||
The following examlpe shows how to do so:
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
@@ -222,8 +218,7 @@ future.addCallback(new ListenableFutureCallback<SendResult<Integer, String>>() {
|
||||
See the Kafka API documentation for information about those objects.
|
||||
|
||||
If you wish to block the sending thread to await the result, you can invoke the future's `get()` method.
|
||||
You may wish to invoke `flush()` before waiting or, for convenience, the template has a constructor with an `autoFlush`
|
||||
parameter that causes the template to `flush()` on each send.
|
||||
You may wish to invoke `flush()` before waiting or, for convenience, the template has a constructor with an `autoFlush` parameter that causes the template to `flush()` on each send.
|
||||
Note, however, that flushing likely significantly reduces performance.
|
||||
|
||||
====== Examples
|
||||
@@ -610,34 +605,26 @@ public interface BatchAcknowledgingConsumerAwareMessageListener<K, V> extends Ba
|
||||
}
|
||||
----
|
||||
|
||||
<1> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
<1> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
|
||||
<2> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using one of the manual <<committing-offsets,commit methods>>.
|
||||
<2> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using one of the manual <<committing-offsets,commit methods>>.
|
||||
|
||||
<3> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
<3> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
Access to the `Consumer` object is provided.
|
||||
|
||||
<4> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using one of the manual <<committing-offsets,commit methods>>.
|
||||
<4> Use this interface for processing individual `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using one of the manual <<committing-offsets,commit methods>>.
|
||||
Access to the `Consumer` object is provided.
|
||||
|
||||
<5> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
<5> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
`AckMode.RECORD` is not supported when you use this interface, since the listener is given the complete batch.
|
||||
|
||||
<6> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using one of the manual <<committing-offsets,commit methods>>.
|
||||
<6> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using one of the manual <<committing-offsets,commit methods>>.
|
||||
|
||||
<7> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
<7> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using auto-commit or one of the container-managed <<committing-offsets,commit methods>>.
|
||||
`AckMode.RECORD` is not supported when you use this interface, since the listener is given the complete batch.
|
||||
Access to the `Consumer` object is provided.
|
||||
|
||||
<8> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when
|
||||
using one of the manual <<committing-offsets,commit methods>>.
|
||||
<8> Use this interface for processing all `ConsumerRecord` instances received from the Kafka consumer `poll()` operation when using one of the manual <<committing-offsets,commit methods>>.
|
||||
Access to the `Consumer` object is provided.
|
||||
====
|
||||
|
||||
@@ -653,8 +640,7 @@ Two `MessageListenerContainer` implementations are provided:
|
||||
* `ConcurrentMessageListenerContainer`
|
||||
|
||||
The `KafkaMessageListenerContainer` receives all message from all topics or partitions on a single thread.
|
||||
The `ConcurrentMessageListenerContainer` delegates to one or more `KafkaMessageListenerContainer` instances to provide
|
||||
multi-threaded consumption.
|
||||
The `ConcurrentMessageListenerContainer` delegates to one or more `KafkaMessageListenerContainer` instances to provide multi-threaded consumption.
|
||||
|
||||
[[kafka-container]]
|
||||
====== Using `KafkaMessageListenerContainer`
|
||||
@@ -673,8 +659,7 @@ public KafkaMessageListenerContainer(ConsumerFactory<K, V> consumerFactory,
|
||||
----
|
||||
====
|
||||
|
||||
Each takes a `ConsumerFactory` and information about topics and partitions, as well as other configuration in a `ContainerProperties`
|
||||
object.
|
||||
Each takes a `ConsumerFactory` and information about topics and partitions, as well as other configuration in a `ContainerProperties` object.
|
||||
The second constructor is used by the `ConcurrentMessageListenerContainer` (<<using-ConcurrentMessageListenerContainer,described later>>) to distribute `TopicPartitionInitialOffset` across the consumer instances.
|
||||
`ContainerProperties` has the following constructors:
|
||||
|
||||
@@ -689,15 +674,13 @@ public ContainerProperties(Pattern topicPattern)
|
||||
----
|
||||
====
|
||||
|
||||
The first constructor takes an array of `TopicPartitionInitialOffset` arguments to explicitly instruct the container about which partitions to use
|
||||
(using the consumer `assign()` method) and with an optional initial offset.
|
||||
The first constructor takes an array of `TopicPartitionInitialOffset` arguments to explicitly instruct the container about which partitions to use (using the consumer `assign()` method) and with an optional initial offset.
|
||||
A positive value is an absolute offset by default.
|
||||
A negative value is relative to the current last offset within a partition by default.
|
||||
A constructor for `TopicPartitionInitialOffset` that takes an additional `boolean` argument is provided.
|
||||
If this is `true`, the initial offsets (positive or negative) are relative to the current position for this consumer.
|
||||
The offsets are applied when the container is started.
|
||||
The second takes an array of topics, and Kafka allocates the partitions based on the `group.id` property -- distributing
|
||||
partitions across the group.
|
||||
The second takes an array of topics, and Kafka allocates the partitions based on the `group.id` property -- distributing partitions across the group.
|
||||
The third uses a regex `Pattern` to select the topics.
|
||||
|
||||
To assign a `MessageListener` to a container, you can use the `ContainerProps.setMessageListener` method when creating the Container.
|
||||
@@ -732,7 +715,7 @@ This prevents the container from starting if any of the configured topics are no
|
||||
It does not apply if the container is configured to listen to a topic pattern (regex).
|
||||
Previously, the container threads looped within the `consumer.poll()` method waiting for the topic to appear while logging many messages.
|
||||
Aside from the logs, there was no indication that there was a problem.
|
||||
To restore the previous behavior, you canset the property to `false`.
|
||||
To restore the previous behavior, you can set the property to `false`.
|
||||
|
||||
[[using-ConcurrentMessageListenerContainer]]
|
||||
====== Using `ConcurrentMessageListenerContainer`
|
||||
@@ -774,13 +757,11 @@ org.apache.kafka.clients.consumer.RoundRobinAssignor
|
||||
=====
|
||||
====
|
||||
|
||||
For the second constructor, the `ConcurrentMessageListenerContainer` distributes the `TopicPartition` instances across the
|
||||
delegate `KafkaMessageListenerContainer` instances.
|
||||
For the second constructor, the `ConcurrentMessageListenerContainer` distributes the `TopicPartition` instances across the delegate `KafkaMessageListenerContainer` instances.
|
||||
|
||||
If, say, six `TopicPartition` instances are provided and the `concurrency` is `3`; each container gets two partitions.
|
||||
For five `TopicPartition` instances, two containers get two partitions, and the third gets one.
|
||||
If the `concurrency` is greater than the number of `TopicPartitions`, the `concurrency` is adjusted down such that
|
||||
each container gets one partition.
|
||||
If the `concurrency` is greater than the number of `TopicPartitions`, the `concurrency` is adjusted down such that each container gets one partition.
|
||||
|
||||
NOTE: The `client.id` property (if set) is appended with `-n` where `n` is the consumer instance that corresponds to the concurrency.
|
||||
This is required to provide unique names for MBeans when JMX is enabled.
|
||||
@@ -793,8 +774,7 @@ The metrics are grouped into the `Map<MetricName, ? extends Metric>` by the `cli
|
||||
====== Committing Offsets
|
||||
|
||||
Several options are provided for committing offsets.
|
||||
If the `enable.auto.commit` consumer property is `true`, Kafka auto-commits the offsets according to its
|
||||
configuration.
|
||||
If the `enable.auto.commit` consumer property is `true`, Kafka auto-commits the offsets according to its configuration.
|
||||
If it is `false`, the containers support several `AckMode` settings (described in the next list).
|
||||
|
||||
The consumer `poll()` method returns one or more `ConsumerRecords`.
|
||||
@@ -803,15 +783,12 @@ The following lists describes the action taken by the container for each `AckMod
|
||||
|
||||
* `RECORD`: Commit the offset when the listener returns after processing the record.
|
||||
* `BATCH`: Commit the offset when all the records returned by the `poll()` have been processed.
|
||||
* `TIME`: Commit the offset when all the records returned by the `poll()` have been processed, as long as the `ackTime`
|
||||
since the last commit has been exceeded.
|
||||
* `COUNT`: Commit the offset when all the records returned by the `poll()` have been processed, as long as `ackCount`
|
||||
records have been received since the last commit.
|
||||
* `TIME`: Commit the offset when all the records returned by the `poll()` have been processed, as long as the `ackTime` since the last commit has been exceeded.
|
||||
* `COUNT`: Commit the offset when all the records returned by the `poll()` have been processed, as long as `ackCount` records have been received since the last commit.
|
||||
* `COUNT_TIME`: Similar to `TIME` and `COUNT`, but the commit is performed if either condition is `true`.
|
||||
* `MANUAL`: The message listener is responsible to `acknowledge()` the `Acknowledgment`.
|
||||
After that, the same semantics as `BATCH` are applied.
|
||||
* `MANUAL_IMMEDIATE`: Commit the offset immediately when the `Acknowledgment.acknowledge()` method is called by the
|
||||
listener.
|
||||
* `MANUAL_IMMEDIATE`: Commit the offset immediately when the `Acknowledgment.acknowledge()` method is called by the listener.
|
||||
|
||||
NOTE: `MANUAL`, and `MANUAL_IMMEDIATE` require the listener to be an `AcknowledgingMessageListener` or a `BatchAcknowledgingMessageListener`.
|
||||
See <<message-listeners, Message Listeners>>.
|
||||
@@ -1679,8 +1656,7 @@ You must configure the appropriate type to match the <<message-listeners,message
|
||||
|
||||
To retry deliveries, a convenient listener adapter `RetryingMessageListenerAdapter` is provided.
|
||||
|
||||
You can configure it with a `RetryTemplate` and `RecoveryCallback<Void>` - see the https://github.com/spring-projects/spring-retry[spring-retry]
|
||||
project for information about these components.
|
||||
You can configure it with a `RetryTemplate` and `RecoveryCallback<Void>` - see the https://github.com/spring-projects/spring-retry[spring-retry] project for information about these components.
|
||||
If a recovery callback is not provided, the exception is thrown to the container after retries are exhausted.
|
||||
In that case, the `ErrorHandler` is invoked, if configured, or logged otherwise.
|
||||
|
||||
@@ -1773,8 +1749,7 @@ You can capture these events by implementing `ApplicationListener` -- either a g
|
||||
You can also use `@EventListener`, introduced in Spring Framework 4.2.
|
||||
|
||||
The next example combines `@KafkaListener` and `@EventListener` into a single class.
|
||||
You should understand that the application listener gets events for all containers, so you may need to
|
||||
check the listener ID if you want to take specific action based on which container is idle.
|
||||
You should understand that the application listener gets events for all containers, so you may need to check the listener ID if you want to take specific action based on which container is idle.
|
||||
You can also use the `@EventListener` `condition` for this purpose.
|
||||
|
||||
See <<events>> for information about event properties.
|
||||
@@ -2077,17 +2052,13 @@ props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
----
|
||||
====
|
||||
|
||||
For more complex or particular cases, the `KafkaConsumer` (and, therefore, `KafkaProducer`) provides overloaded
|
||||
constructors to accept `Serializer` and `Deserializer` instances for `keys` and `values`, respectively.
|
||||
For more complex or particular cases, the `KafkaConsumer` (and, therefore, `KafkaProducer`) provides overloaded constructors to accept `Serializer` and `Deserializer` instances for `keys` and `values`, respectively.
|
||||
|
||||
When you use this API, the `DefaultKafkaProducerFactory` and `DefaultKafkaConsumerFactory` also provide properties (through constructors or setter methods) to inject custom `Serializer` and `Deserializer` instances into the target `Producer` or `Consumer`.
|
||||
|
||||
Spring for Apache Kafka also provides `JsonSerializer` and `JsonDeserializer` implementations that are based on the
|
||||
Jackson JSON object mapper.
|
||||
Spring for Apache Kafka also provides `JsonSerializer` and `JsonDeserializer` implementations that are based on the Jackson JSON object mapper.
|
||||
The `JsonSerializer` allows writing any Java object as a JSON `byte[]`.
|
||||
The `JsonDeserializer`
|
||||
requires an additional `Class<?> targetType` argument to allow the deserialization of a consumed `byte[]` to the proper target
|
||||
object.
|
||||
The `JsonDeserializer` requires an additional `Class<?> targetType` argument to allow the deserialization of a consumed `byte[]` to the proper target object.
|
||||
The following example shows how to create a `JsonDeserializer`:
|
||||
|
||||
====
|
||||
@@ -2098,8 +2069,7 @@ JsonDeserializer<Thing> thingDeserializer = new JsonDeserializer<>(Thing.class);
|
||||
====
|
||||
|
||||
You can customize both `JsonSerializer` and `JsonDeserializer` with an `ObjectMapper`.
|
||||
You can also extend them to implement some particular configuration logic in the
|
||||
`configure(Map<String, ?> configs, boolean isKey)` method.
|
||||
You can also extend them to implement some particular configuration logic in the `configure(Map<String, ?> configs, boolean isKey)` method.
|
||||
|
||||
Starting with version 2.1, you can convey type information in record `Headers`, allowing the handling of multiple types.
|
||||
In addition, you can configure the serializer and deserializer by using the following Kafka properties:
|
||||
@@ -2196,12 +2166,9 @@ DefaultKafkaConsumerFactory<Integer, Cat1> cf = new DefaultKafkaConsumerFactory<
|
||||
|
||||
===== Spring Messaging Message Conversion
|
||||
|
||||
Although the `Serializer` and `Deserializer` API is quite simple and flexible from the low-level Kafka `Consumer` and
|
||||
`Producer` perspective, you might need more flexibility at the Spring Messaging level, when using either `@KafkaListener` or <<si-kafka,Spring Integration>>.
|
||||
To let you easily convert to and from `org.springframework.messaging.Message`, Spring for Apache Kafka provides a `MessageConverter`
|
||||
abstraction with the `MessagingMessageConverter` implementation and its `StringJsonMessageConverter` and `BytesJsonMessageConverter` customization.
|
||||
You can inject the `MessageConverter` into a `KafkaTemplate` instance directly and by using
|
||||
`AbstractKafkaListenerContainerFactory` bean definition for the `@KafkaListener.containerFactory()` property.
|
||||
Although the `Serializer` and `Deserializer` API is quite simple and flexible from the low-level Kafka `Consumer` and `Producer` perspective, you might need more flexibility at the Spring Messaging level, when using either `@KafkaListener` or <<si-kafka,Spring Integration>>.
|
||||
To let you easily convert to and from `org.springframework.messaging.Message`, Spring for Apache Kafka provides a `MessageConverter` abstraction with the `MessagingMessageConverter` implementation and its `StringJsonMessageConverter` and `BytesJsonMessageConverter` customization.
|
||||
You can inject the `MessageConverter` into a `KafkaTemplate` instance directly and by using `AbstractKafkaListenerContainerFactory` bean definition for the `@KafkaListener.containerFactory()` property.
|
||||
The following example shows how to do so:
|
||||
|
||||
====
|
||||
@@ -2381,19 +2348,15 @@ public void listen1(List<Message<Foo>> fooMessages) {
|
||||
|
||||
===== `ConversionService` Customization
|
||||
|
||||
Starting with version 2.1.1, the `org.springframework.core.convert.ConversionService` used by the default
|
||||
`o.s.messaging.handler.annotation.support.MessageHandlerMethodFactory` to resolve parameters for the invocation
|
||||
of a listener method is supplied with all beans that implement any of the following interfaces:
|
||||
Starting with version 2.1.1, the `org.springframework.core.convert.ConversionService` used by the default `o.s.messaging.handler.annotation.support.MessageHandlerMethodFactory` to resolve parameters for the invocation of a listener method is supplied with all beans that implement any of the following interfaces:
|
||||
|
||||
* `org.springframework.core.convert.converter.Converter`
|
||||
* `org.springframework.core.convert.converter.GenericConverter`
|
||||
* `org.springframework.format.Formatter`
|
||||
|
||||
This lets you further customize listener deserialization without changing the default configuration for
|
||||
`ConsumerFactory` and `KafkaListenerContainerFactory`.
|
||||
This lets you further customize listener deserialization without changing the default configuration for `ConsumerFactory` and `KafkaListenerContainerFactory`.
|
||||
|
||||
IMPORTANT: Setting a custom `MessageHandlerMethodFactory` on the `KafkaListenerEndpointRegistrar` through a
|
||||
`KafkaListenerConfigurer` bean disables this feature.
|
||||
IMPORTANT: Setting a custom `MessageHandlerMethodFactory` on the `KafkaListenerEndpointRegistrar` through a `KafkaListenerConfigurer` bean disables this feature.
|
||||
|
||||
[[headers]]
|
||||
==== Message Headers
|
||||
@@ -2550,7 +2513,7 @@ For convenience, the static `KafkaNull.INSTANCE` is provided.
|
||||
When you use a message listener container, the received `ConsumerRecord` has a `null` `value()`.
|
||||
|
||||
To configure the `@KafkaListener` to handle `null` payloads, you must use the `@Payload` annotation with `required = false`.
|
||||
If itis a tombstone message for a compacted log, you usually also need the key so that your application can determine which key was "`deleted`".
|
||||
If it is a tombstone message for a compacted log, you usually also need the key so that your application can determine which key was "`deleted`".
|
||||
The following example shows such a configuration:
|
||||
|
||||
====
|
||||
|
||||
@@ -22,8 +22,7 @@ Sender applications can publish to Kafka by using Spring Integration messages, w
|
||||
* The payload of the Spring Integration message is used to populate the payload of the Kafka message.
|
||||
* By default, the `kafka_messageKey` header of the Spring Integration message is used to populate the key of the Kafka message.
|
||||
|
||||
You can customize the target topic and partition for publishing the message through the `kafka_topic`
|
||||
and `kafka_partitionId` headers, respectively.
|
||||
You can customize the target topic and partition for publishing the message through the `kafka_topic` and `kafka_partitionId` headers, respectively.
|
||||
|
||||
In addition, the `<int-kafka:outbound-channel-adapter>` provides the ability to extract the key, target topic, and target partition by applying SpEL expressions on the outbound message.
|
||||
To that end, it supports three mutually exclusive pairs of attributes:
|
||||
@@ -34,18 +33,13 @@ To that end, it supports three mutually exclusive pairs of attributes:
|
||||
|
||||
These let you specify `topic`, `message-key`, and `partition-id`, respectively, as static values on the adapter or to dynamically evaluate their values at runtime against the request message.
|
||||
|
||||
IMPORTANT: The `KafkaHeaders` interface (provided by `spring-kafka`) contains constants used for interacting with
|
||||
headers.
|
||||
IMPORTANT: The `KafkaHeaders` interface (provided by `spring-kafka`) contains constants used for interacting with headers.
|
||||
The `messageKey` and `topic` default headers now require a `kafka_` prefix.
|
||||
When migrating from an earlier version that used the old headers, you need to specify
|
||||
`message-key-expression="headers['messageKey']"` and `topic-expression="headers['topic']"` on the
|
||||
`<int-kafka:outbound-channel-adapter>`.
|
||||
Alternatively, you can change the headers upstream to
|
||||
the new headers from `KafkaHeaders` by using a `<header-enricher>` or a `MessageBuilder`.
|
||||
When migrating from an earlier version that used the old headers, you need to specify `message-key-expression="headers['messageKey']"` and `topic-expression="headers['topic']"` on the `<int-kafka:outbound-channel-adapter>`.
|
||||
Alternatively, you can change the headers upstream to the new headers from `KafkaHeaders` by using a `<header-enricher>` or a `MessageBuilder`.
|
||||
If you use constant values, you can also configure them on the adapter by using `topic` and `message-key`.
|
||||
|
||||
NOTE : If the adapter is configured with a topic or message key (either with a constant or expression), those are used
|
||||
and the corresponding header is ignored.
|
||||
NOTE : If the adapter is configured with a topic or message key (either with a constant or expression), those are used and the corresponding header is ignored.
|
||||
If you wish the header to override the configuration, you need to configure it in an expression, such as the following:
|
||||
|
||||
====
|
||||
|
||||
Reference in New Issue
Block a user