Make Twitter modules as auto-configuration
* Fix all the Checkstyle violation in the effected modules * Fix Twitter modules READMEs to reflect a nature of the auto-configuration * Add (and mention in README) new `enabled` properties for various Twitter supplier configurations: by default all of them would be started just by the presence in classpath
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2020 the original author or authors.
|
||||
* Copyright 2015-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.cloud.fn.common.twitter;
|
||||
|
||||
/**
|
||||
* The cursor abstraction.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class Cursor {
|
||||
@@ -24,7 +26,7 @@ public class Cursor {
|
||||
private long cursor = -1;
|
||||
|
||||
public long getCursor() {
|
||||
return cursor;
|
||||
return this.cursor;
|
||||
}
|
||||
|
||||
public void updateCursor(long newCursor) {
|
||||
@@ -33,7 +35,7 @@ public class Cursor {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cursor{cursor=" + cursor + '}';
|
||||
return "Cursor{cursor=" + this.cursor + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2020 the original author or authors.
|
||||
* Copyright 2015-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,26 +31,30 @@ import twitter4j.TwitterStream;
|
||||
import twitter4j.TwitterStreamFactory;
|
||||
import twitter4j.conf.ConfigurationBuilder;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* The auto-configuration for Twitter4J support.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ TwitterConnectionProperties.class })
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(TwitterConnectionProperties.class)
|
||||
public class TwitterConnectionConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TwitterConnectionConfiguration.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(TwitterConnectionConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public twitter4j.conf.Configuration twitterConfiguration(TwitterConnectionProperties properties,
|
||||
Function<TwitterConnectionProperties, ConfigurationBuilder> toConfigurationBuilder) {
|
||||
|
||||
return toConfigurationBuilder.apply(properties).build();
|
||||
}
|
||||
|
||||
@@ -66,7 +70,7 @@ public class TwitterConnectionConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<TwitterConnectionProperties, ConfigurationBuilder> toConfigurationBuilder() {
|
||||
return properties -> new ConfigurationBuilder().setJSONStoreEnabled(properties.isRawJson())
|
||||
return (properties) -> new ConfigurationBuilder().setJSONStoreEnabled(properties.isRawJson())
|
||||
.setDebugEnabled(properties.isDebugEnabled())
|
||||
.setOAuthConsumerKey(properties.getConsumerKey())
|
||||
.setOAuthConsumerSecret(properties.getConsumerSecret())
|
||||
@@ -76,7 +80,7 @@ public class TwitterConnectionConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<Object, Message<byte[]>> json(ObjectMapper mapper) {
|
||||
return objects -> {
|
||||
return (objects) -> {
|
||||
try {
|
||||
String json = mapper.writeValueAsString(objects);
|
||||
|
||||
@@ -84,26 +88,24 @@ public class TwitterConnectionConfiguration {
|
||||
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE)
|
||||
.build();
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
logger.error("Status to JSON conversion error!", e);
|
||||
catch (JsonProcessingException ex) {
|
||||
LOGGER.error("Status to JSON conversion error!", ex);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the raw JSON form of the provided object.
|
||||
*
|
||||
* Note that raw JSON forms can be retrieved only from the same thread invoked the
|
||||
* last method call and will become inaccessible once another method call.
|
||||
* @return Function that can retrieve the raw JSON object from the objects returned by
|
||||
* Retrieves the raw JSON form of the provided object. Note that raw JSON forms can be
|
||||
* retrieved only from the same thread invoked the last method call and will become
|
||||
* inaccessible once another method call.
|
||||
* @return function that can retrieve the raw JSON object from the objects returned by
|
||||
* the Twitter4J's APIs.
|
||||
*/
|
||||
@Bean
|
||||
public Function<Object, Object> rawJsonExtractor() {
|
||||
return response -> {
|
||||
if (response instanceof List) {
|
||||
List responses = (List) response;
|
||||
return (response) -> {
|
||||
if (response instanceof List<?> responses) {
|
||||
List<String> rawJsonList = new ArrayList<>();
|
||||
for (Object object : responses) {
|
||||
rawJsonList.add(TwitterObjectFactory.getRawJSON(object));
|
||||
@@ -119,7 +121,8 @@ public class TwitterConnectionConfiguration {
|
||||
@Bean
|
||||
public Function<Object, Message<byte[]>> managedJson(TwitterConnectionProperties properties,
|
||||
Function<Object, Object> rawJsonExtractor, Function<Object, Message<byte[]>> json) {
|
||||
return list -> (properties.isRawJson()) ? rawJsonExtractor.andThen(json).apply(list) : json.apply(list);
|
||||
|
||||
return (list) -> (properties.isRawJson()) ? rawJsonExtractor.andThen(json).apply(list) : json.apply(list);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2020 the original author or authors.
|
||||
* Copyright 2015-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter4J connection properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ConfigurationProperties("twitter.connection")
|
||||
@@ -65,7 +67,7 @@ public class TwitterConnectionProperties {
|
||||
private boolean rawJson = true;
|
||||
|
||||
public String getConsumerKey() {
|
||||
return consumerKey;
|
||||
return this.consumerKey;
|
||||
}
|
||||
|
||||
public void setConsumerKey(String consumerKey) {
|
||||
@@ -73,7 +75,7 @@ public class TwitterConnectionProperties {
|
||||
}
|
||||
|
||||
public String getConsumerSecret() {
|
||||
return consumerSecret;
|
||||
return this.consumerSecret;
|
||||
}
|
||||
|
||||
public void setConsumerSecret(String consumerSecret) {
|
||||
@@ -81,7 +83,7 @@ public class TwitterConnectionProperties {
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
@@ -89,7 +91,7 @@ public class TwitterConnectionProperties {
|
||||
}
|
||||
|
||||
public String getAccessTokenSecret() {
|
||||
return accessTokenSecret;
|
||||
return this.accessTokenSecret;
|
||||
}
|
||||
|
||||
public void setAccessTokenSecret(String accessTokenSecret) {
|
||||
@@ -97,7 +99,7 @@ public class TwitterConnectionProperties {
|
||||
}
|
||||
|
||||
public boolean isDebugEnabled() {
|
||||
return debugEnabled;
|
||||
return this.debugEnabled;
|
||||
}
|
||||
|
||||
public void setDebugEnabled(boolean debugEnabled) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter4J connection support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.common.twitter;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.cloud.fn.common.twitter.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.function.Function;
|
||||
|
||||
import twitter4j.conf.ConfigurationBuilder;
|
||||
@@ -26,12 +26,16 @@ import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* The test utilities for Twitter applications.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class TwitterTestUtils {
|
||||
|
||||
private static final DefaultResourceLoader RESOURCE_LOADER = new DefaultResourceLoader();
|
||||
|
||||
public Function<ConfigurationBuilder, ConfigurationBuilder> mockTwitterUrls(String baseUrl) {
|
||||
return configBuilder -> {
|
||||
return (configBuilder) -> {
|
||||
configBuilder.setRestBaseURL(baseUrl + "/");
|
||||
configBuilder.setStreamBaseURL(baseUrl + "/stream/");
|
||||
configBuilder.setUserStreamBaseURL(baseUrl + "/user/");
|
||||
@@ -51,18 +55,16 @@ public class TwitterTestUtils {
|
||||
|
||||
/**
|
||||
* Load Spring Resource as String.
|
||||
* @param resourcePath Resource path (accepts file:// , classpath:// and http:// uri
|
||||
* schemas)
|
||||
* @return Returns text (UTF8) representation of the resource pointed by the
|
||||
* resourcePath
|
||||
* @param resourcePath accepts file://, classpath:// and http:// uri schemas
|
||||
* @return the text (UTF8) representation of the resource pointed by the resourcePath
|
||||
*/
|
||||
public static String asString(String resourcePath) {
|
||||
try {
|
||||
return StreamUtils.copyToString(new DefaultResourceLoader().getResource(resourcePath).getInputStream(),
|
||||
Charset.forName("UTF-8"));
|
||||
return StreamUtils.copyToString(RESOURCE_LOADER.getResource(resourcePath).getInputStream(),
|
||||
StandardCharsets.UTF_8);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("Can not load resource:" + resourcePath, e);
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException("Can not load resource:" + resourcePath, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter applications utility classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.common.twitter.util;
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## 1. Twitter Status Update Consumer.
|
||||
|
||||
Updates the authenticating user's current text (e.g Tweeting).
|
||||
Updates the authenticating user's current text (e.g. Tweeting).
|
||||
|
||||
NOTE: For each update attempt, the update text is compared with the authenticating user's recent Tweets.
|
||||
Any attempt that would result in duplication will be blocked, resulting in a 403 error.
|
||||
@@ -18,7 +18,7 @@ You can find details for the Update API here: https://developer.twitter.com/en/d
|
||||
|
||||
### 1.1 Beans for injection
|
||||
|
||||
You can import `TwitterUpdateConsumerConfiguration` in the application and then inject the following beans.
|
||||
The `TwitterUpdateConsumerConfiguration` auto-configuration provides this beans:
|
||||
|
||||
- `Consumer<StatusUpdate> updateStatus` - if you have an `StatusUpdate` instance you can use the `updateStatus` to apply it.
|
||||
|
||||
@@ -47,16 +47,16 @@ See this https://github.com/spring-cloud/stream-applications/blob/master/applica
|
||||
Send Direct Messages to a specified user from the authenticating user.
|
||||
Requires a JSON POST body and `Content-Type` header to be set to `application/json`.
|
||||
|
||||
NOTE: When a message is received from a user you may send up to 5 messages in response within a 24 hour window.
|
||||
Each message received resets the 24 hour window and the 5 allotted messages.
|
||||
Sending a 6th message within a 24 hour window or sending a message outside of a 24 hour window will count towards rate-limiting.
|
||||
NOTE: When a message is received from a user you may send up to 5 messages in response within a 24-hour window.
|
||||
Each message received resets the 24-hour window and the 5 allotted messages.
|
||||
Sending a 6th message within a 24-hour window or sending a message outside a 24-hour window will count towards rate-limiting.
|
||||
This behavior only applies when using the POST direct_messages/events/new endpoint.
|
||||
|
||||
SpEL expressions are used to compute the request parameters from the input message.
|
||||
|
||||
### 2.1 Beans for injection
|
||||
|
||||
You can import `TwitterMessageConsumerConfiguration` in the application and then inject the following bean.
|
||||
The `TwitterMessageConsumerConfiguration` auto-configuration provides this beans:
|
||||
|
||||
- `Consumer<Message<?>> sendDirectMessageConsumer`
|
||||
|
||||
@@ -92,7 +92,7 @@ Every operation type has its own parameters.
|
||||
|
||||
### 3.1 Beans for injection
|
||||
|
||||
You can import `TwitterFriendshipsConsumerConfiguration` in the application and then inject the following bean.
|
||||
The `TwitterFriendshipsConsumerConfiguration` auto-configuration provides this beans:
|
||||
|
||||
- `Consumer<Message<?>> friendshipConsumer`
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,26 +21,26 @@ import java.util.function.Consumer;
|
||||
import twitter4j.Twitter;
|
||||
import twitter4j.TwitterException;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* The auto-configuration for Twitter Friendships.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfiguration(after = TwitterConnectionConfiguration.class)
|
||||
@EnableConfigurationProperties(TwitterFriendshipsConsumerProperties.class)
|
||||
@Import(TwitterConnectionConfiguration.class)
|
||||
public class TwitterFriendshipsConsumerConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("Duplicates")
|
||||
public Consumer<Message<?>> friendshipConsumer(TwitterFriendshipsConsumerProperties properties, Twitter twitter) {
|
||||
|
||||
return message -> {
|
||||
return (message) -> {
|
||||
try {
|
||||
TwitterFriendshipsConsumerProperties.OperationType type = properties.getType()
|
||||
.getValue(message, TwitterFriendshipsConsumerProperties.OperationType.class);
|
||||
@@ -49,43 +49,39 @@ public class TwitterFriendshipsConsumerConfiguration {
|
||||
if (properties.getUserId() != null) {
|
||||
Long userId = properties.getUserId().getValue(message, long.class);
|
||||
switch (type) {
|
||||
case create:
|
||||
case create -> {
|
||||
boolean follow = properties.getCreate().getFollow().getValue(message, boolean.class);
|
||||
twitter.createFriendship(userId, follow);
|
||||
return;
|
||||
|
||||
case update:
|
||||
}
|
||||
case update -> {
|
||||
boolean enableDeviceNotification = properties.getUpdate()
|
||||
.getDevice()
|
||||
.getValue(message, boolean.class);
|
||||
boolean retweets = properties.getUpdate().getRetweets().getValue(message, boolean.class);
|
||||
twitter.updateFriendship(userId, enableDeviceNotification, retweets);
|
||||
return;
|
||||
|
||||
case destroy:
|
||||
}
|
||||
case destroy -> {
|
||||
twitter.destroyFriendship(userId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (properties.getScreenName() != null) {
|
||||
String screenName = properties.getScreenName().getValue(message, String.class);
|
||||
switch (type) {
|
||||
case create:
|
||||
case create -> {
|
||||
boolean follow = properties.getCreate().getFollow().getValue(message, boolean.class);
|
||||
twitter.createFriendship(screenName, follow);
|
||||
return;
|
||||
|
||||
case update:
|
||||
}
|
||||
case update -> {
|
||||
boolean enableDeviceNotification = properties.getUpdate()
|
||||
.getDevice()
|
||||
.getValue(message, boolean.class);
|
||||
boolean retweets = properties.getUpdate().getRetweets().getValue(message, boolean.class);
|
||||
twitter.updateFriendship(screenName, enableDeviceNotification, retweets);
|
||||
return;
|
||||
|
||||
case destroy:
|
||||
}
|
||||
case destroy -> {
|
||||
twitter.destroyFriendship(screenName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,11 +22,15 @@ import jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.expression.ValueExpression;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter friendships properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties("twitter.friendships.update")
|
||||
@@ -58,15 +62,15 @@ public class TwitterFriendshipsConsumerProperties {
|
||||
/**
|
||||
* Additional properties for the Friendships create requests.
|
||||
*/
|
||||
private Create create = new Create();
|
||||
private final Create create = new Create();
|
||||
|
||||
/**
|
||||
* Additional properties for the Friendships update requests.
|
||||
*/
|
||||
private Update update = new Update();
|
||||
private final Update update = new Update();
|
||||
|
||||
public Expression getScreenName() {
|
||||
return screenName;
|
||||
return this.screenName;
|
||||
}
|
||||
|
||||
public void setScreenName(Expression screenName) {
|
||||
@@ -74,7 +78,7 @@ public class TwitterFriendshipsConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getUserId() {
|
||||
return userId;
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
public void setUserId(Expression userId) {
|
||||
@@ -82,7 +86,7 @@ public class TwitterFriendshipsConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getType() {
|
||||
return type;
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(Expression type) {
|
||||
@@ -90,11 +94,11 @@ public class TwitterFriendshipsConsumerProperties {
|
||||
}
|
||||
|
||||
public Create getCreate() {
|
||||
return create;
|
||||
return this.create;
|
||||
}
|
||||
|
||||
public Update getUpdate() {
|
||||
return update;
|
||||
return this.update;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "Either userId or screenName must be provided")
|
||||
@@ -108,10 +112,10 @@ public class TwitterFriendshipsConsumerProperties {
|
||||
* The ID of the user to follow (boolean).
|
||||
*/
|
||||
@NotNull
|
||||
private Expression follow = new SpelExpressionParser().parseExpression("'true'");
|
||||
private Expression follow = new ValueExpression<>(true);
|
||||
|
||||
public Expression getFollow() {
|
||||
return follow;
|
||||
return this.follow;
|
||||
}
|
||||
|
||||
public void setFollow(Expression follow) {
|
||||
@@ -126,16 +130,16 @@ public class TwitterFriendshipsConsumerProperties {
|
||||
* Enable/disable device notifications from the target user.
|
||||
*/
|
||||
@NotNull
|
||||
private Expression device = new SpelExpressionParser().parseExpression("'true'");
|
||||
private Expression device = new ValueExpression<>(true);
|
||||
|
||||
/**
|
||||
* Enable/disable Retweets from the target user.
|
||||
*/
|
||||
@NotNull
|
||||
private Expression retweets = new SpelExpressionParser().parseExpression("'true'");
|
||||
private Expression retweets = new ValueExpression<>(true);
|
||||
|
||||
public Expression getDevice() {
|
||||
return device;
|
||||
return this.device;
|
||||
}
|
||||
|
||||
public void setDevice(Expression device) {
|
||||
@@ -143,7 +147,7 @@ public class TwitterFriendshipsConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getRetweets() {
|
||||
return retweets;
|
||||
return this.retweets;
|
||||
}
|
||||
|
||||
public void setRetweets(Expression retweets) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter friendship support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.consumer.twitter.friendship;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,27 +23,29 @@ import org.apache.commons.logging.LogFactory;
|
||||
import twitter4j.Twitter;
|
||||
import twitter4j.TwitterException;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* The auto-configuration for Twitter messages.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfiguration(after = TwitterConnectionConfiguration.class)
|
||||
@EnableConfigurationProperties(TwitterMessageConsumerProperties.class)
|
||||
@Import(TwitterConnectionConfiguration.class)
|
||||
public class TwitterMessageConsumerConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TwitterMessageConsumerConfiguration.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(TwitterMessageConsumerConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public Consumer<Message<?>> sendDirectMessageConsumer(TwitterMessageConsumerProperties messageProperties,
|
||||
Twitter twitter) {
|
||||
return message -> {
|
||||
|
||||
return (message) -> {
|
||||
try {
|
||||
String messageText = messageProperties.getText().getValue(message, String.class);
|
||||
|
||||
@@ -63,8 +65,8 @@ public class TwitterMessageConsumerConfiguration {
|
||||
throw new RuntimeException("Either the UserId or screenName must be set");
|
||||
}
|
||||
}
|
||||
catch (TwitterException e) {
|
||||
logger.error("Failed to process message:" + message, e);
|
||||
catch (TwitterException ex) {
|
||||
LOGGER.error("Failed to process message: " + message, ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter messages properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ConfigurationProperties("twitter.message.update")
|
||||
@@ -52,7 +54,7 @@ public class TwitterMessageConsumerProperties {
|
||||
private Expression mediaId;
|
||||
|
||||
public Expression getUserId() {
|
||||
return userId;
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
public void setUserId(Expression userId) {
|
||||
@@ -64,11 +66,11 @@ public class TwitterMessageConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getText() {
|
||||
return text;
|
||||
return this.text;
|
||||
}
|
||||
|
||||
public Expression getScreenName() {
|
||||
return screenName;
|
||||
return this.screenName;
|
||||
}
|
||||
|
||||
public void setScreenName(Expression screenName) {
|
||||
@@ -76,7 +78,7 @@ public class TwitterMessageConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getMediaId() {
|
||||
return mediaId;
|
||||
return this.mediaId;
|
||||
}
|
||||
|
||||
public void setMediaId(Expression mediaId) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter messages support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.consumer.twitter.message;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,35 +27,35 @@ import twitter4j.StatusUpdate;
|
||||
import twitter4j.Twitter;
|
||||
import twitter4j.TwitterException;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* The auto-configuration for Twitter messages.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfiguration(after = TwitterConnectionConfiguration.class)
|
||||
@EnableConfigurationProperties(TwitterUpdateConsumerProperties.class)
|
||||
@Import(TwitterConnectionConfiguration.class)
|
||||
public class TwitterUpdateConsumerConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TwitterUpdateConsumerConfiguration.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(TwitterUpdateConsumerConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public Consumer<StatusUpdate> updateStatus(Twitter twitter) {
|
||||
return statusUpdate -> {
|
||||
return (statusUpdate) -> {
|
||||
try {
|
||||
Status status = twitter.updateStatus(statusUpdate);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(status);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(status);
|
||||
}
|
||||
}
|
||||
catch (TwitterException e) {
|
||||
logger.error("Failed apply update status: " + statusUpdate, e);
|
||||
catch (TwitterException ex) {
|
||||
LOGGER.error("Failed apply update status: " + statusUpdate, ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class TwitterUpdateConsumerConfiguration {
|
||||
public Function<Message<?>, StatusUpdate> messageToStatusUpdateFunction(
|
||||
TwitterUpdateConsumerProperties updateProperties) {
|
||||
|
||||
return message -> {
|
||||
return (message) -> {
|
||||
|
||||
String updateText = updateProperties.getText().getValue(message, String.class);
|
||||
|
||||
@@ -106,7 +106,8 @@ public class TwitterUpdateConsumerConfiguration {
|
||||
@Bean
|
||||
public Consumer<Message<?>> twitterStatusUpdateConsumer(Function<Message<?>, StatusUpdate> statusUpdateQuery,
|
||||
Consumer<StatusUpdate> updateStatus) {
|
||||
return message -> updateStatus.accept(statusUpdateQuery.apply(message));
|
||||
|
||||
return (message) -> updateStatus.accept(statusUpdateQuery.apply(message));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter update properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ConfigurationProperties("twitter.update")
|
||||
@@ -44,9 +46,9 @@ public class TwitterUpdateConsumerProperties {
|
||||
* (SpEL expression) In order for a URL to not be counted in the text body of an
|
||||
* extended Tweet, provide a URL as a Tweet attachment. This URL must be a Tweet
|
||||
* permalink, or Direct Message deep link. Arbitrary, non-Twitter URLs must remain in
|
||||
* the text text. URLs passed to the attachment_url parameter not matching either a
|
||||
* Tweet permalink or Direct Message deep link will fail at Tweet creation and cause
|
||||
* an exception.
|
||||
* the text. URLs passed to the attachment_url parameter not matching either a Tweet
|
||||
* permalink or Direct Message deep link will fail at Tweet creation and cause an
|
||||
* exception.
|
||||
*/
|
||||
private Expression attachmentUrl;
|
||||
|
||||
@@ -58,12 +60,10 @@ public class TwitterUpdateConsumerProperties {
|
||||
/**
|
||||
* (SpEL expression) The ID of an existing text that the update is in reply to. Note:
|
||||
* This parameter will be ignored unless the author of the Tweet this parameter
|
||||
* references is mentioned within the text text. Therefore, you must
|
||||
* include @username, where username is the author of the referenced Tweet, within the
|
||||
* update.
|
||||
*
|
||||
* When inReplyToStatusId is set the auto_populate_reply_metadata is automatically set
|
||||
* as well. Later ensures that leading @mentions will be looked up from the original
|
||||
* references is mentioned within the text. Therefore, you must include @username,
|
||||
* where username is the author of the referenced Tweet, within the update. When
|
||||
* inReplyToStatusId is set the auto_populate_reply_metadata is automatically set as
|
||||
* well. Later ensures that leading @mentions will be looked up from the original
|
||||
* Tweet, and added to the new Tweet from there. This wil append @mentions into the
|
||||
* metadata of an extended Tweet as a reply chain grows, until the limit on @mentions
|
||||
* is reached. In cases where the original Tweet has been deleted, the reply will
|
||||
@@ -72,8 +72,8 @@ public class TwitterUpdateConsumerProperties {
|
||||
private Expression inReplyToStatusId;
|
||||
|
||||
/**
|
||||
* (SpEL expression) Whether or not to put a pin on the exact coordinates a Tweet has
|
||||
* been sent from.
|
||||
* (SpEL expression) Whether to put a pin on the exact coordinates a Tweet has been
|
||||
* sent from.
|
||||
*/
|
||||
private Expression displayCoordinates;
|
||||
|
||||
@@ -91,7 +91,7 @@ public class TwitterUpdateConsumerProperties {
|
||||
private final Location location = new Location();
|
||||
|
||||
public Expression getText() {
|
||||
return text;
|
||||
return this.text;
|
||||
}
|
||||
|
||||
public void setText(Expression text) {
|
||||
@@ -99,7 +99,7 @@ public class TwitterUpdateConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getAttachmentUrl() {
|
||||
return attachmentUrl;
|
||||
return this.attachmentUrl;
|
||||
}
|
||||
|
||||
public void setAttachmentUrl(Expression attachmentUrl) {
|
||||
@@ -107,7 +107,7 @@ public class TwitterUpdateConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getPlaceId() {
|
||||
return placeId;
|
||||
return this.placeId;
|
||||
}
|
||||
|
||||
public void setPlaceId(Expression placeId) {
|
||||
@@ -115,7 +115,7 @@ public class TwitterUpdateConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getInReplyToStatusId() {
|
||||
return inReplyToStatusId;
|
||||
return this.inReplyToStatusId;
|
||||
}
|
||||
|
||||
public void setInReplyToStatusId(Expression inReplyToStatusId) {
|
||||
@@ -123,7 +123,7 @@ public class TwitterUpdateConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getDisplayCoordinates() {
|
||||
return displayCoordinates;
|
||||
return this.displayCoordinates;
|
||||
}
|
||||
|
||||
public void setDisplayCoordinates(Expression displayCoordinates) {
|
||||
@@ -131,7 +131,7 @@ public class TwitterUpdateConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getMediaIds() {
|
||||
return mediaIds;
|
||||
return this.mediaIds;
|
||||
}
|
||||
|
||||
public void setMediaIds(Expression mediaIds) {
|
||||
@@ -139,13 +139,13 @@ public class TwitterUpdateConsumerProperties {
|
||||
}
|
||||
|
||||
public Location getLocation() {
|
||||
return location;
|
||||
return this.location;
|
||||
}
|
||||
|
||||
@AssertTrue(message = "Lat and Long must be set together or both not being set")
|
||||
public boolean validateLatLon() {
|
||||
return (this.getLocation().getLat() != null && this.getLocation().getLon() != null)
|
||||
|| (this.getLocation().getLat() == null && this.getLocation().getLon() == null);
|
||||
return (getLocation().getLat() != null && getLocation().getLon() != null)
|
||||
|| (getLocation().getLat() == null && getLocation().getLon() == null);
|
||||
}
|
||||
|
||||
public static class Location {
|
||||
@@ -161,12 +161,12 @@ public class TwitterUpdateConsumerProperties {
|
||||
* The longitude of the location this Tweet refers to. The valid ranges for
|
||||
* longitude are -180.0 to +180.0 (East is positive) inclusive. This parameter
|
||||
* will be ignored if outside that range, if it is not a number, if geo_enabled is
|
||||
* disabled, or if there no corresponding lat parameter.
|
||||
* disabled, or if there is no corresponding lat parameter.
|
||||
*/
|
||||
private Expression lon;
|
||||
|
||||
public Expression getLat() {
|
||||
return lat;
|
||||
return this.lat;
|
||||
}
|
||||
|
||||
public void setLat(Expression lat) {
|
||||
@@ -174,7 +174,7 @@ public class TwitterUpdateConsumerProperties {
|
||||
}
|
||||
|
||||
public Expression getLon() {
|
||||
return lon;
|
||||
return this.lon;
|
||||
}
|
||||
|
||||
public void setLon(Expression lon) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter status update support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.consumer.twitter.status.update;
|
||||
@@ -0,0 +1,3 @@
|
||||
org.springframework.cloud.fn.consumer.twitter.friendship.TwitterFriendshipsConsumerConfiguration
|
||||
org.springframework.cloud.fn.consumer.twitter.message.TwitterMessageConsumerConfiguration
|
||||
org.springframework.cloud.fn.consumer.twitter.status.update.TwitterUpdateConsumerConfiguration
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,9 +37,12 @@ import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
public class TwitterUpdateSinkFunctionConfigurationTests {
|
||||
|
||||
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
@Test
|
||||
public void testStatusUpdateConsumer() throws TwitterException {
|
||||
Twitter twitter = mock(Twitter.class);
|
||||
@@ -79,8 +82,7 @@ public class TwitterUpdateSinkFunctionConfigurationTests {
|
||||
}
|
||||
|
||||
private Expression expression(String expressionString) {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
return parser.parseExpression(expressionString);
|
||||
return EXPRESSION_PARSER.parseExpression(expressionString);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ The Twitter's https://developer.twitter.com/en/docs/tweets/search/api-reference/
|
||||
|
||||
Returns a collection of relevant Tweets matching a specified query.
|
||||
|
||||
To enable this supplier, the `twitter.search.enabled` must be set to `true`.
|
||||
|
||||
### 1.1 Beans for injection
|
||||
|
||||
You can import the `TwitterSearchSupplierConfiguration` in the application and then inject the following bean.
|
||||
The `TwitterSearchSupplierConfiguration` auto-configuration provides the following bean:
|
||||
|
||||
`twitterSearchSupplier`
|
||||
|
||||
@@ -28,10 +30,10 @@ Once injected, you can use the `get` method of the `Supplier` to invoke it.
|
||||
The configuration properties prefixed with `twitter.search`.
|
||||
There are also properties that need to be used with the prefix `twitter.connection`.
|
||||
|
||||
The `spring.cloud.stream.poller` properties control the interval between consecutive search requests. Rate Limit - 180 requests per 30 min. window (e.g. ~6 r/m, ~ 1 req / 10 sec.)
|
||||
The `spring.integration.poller` properties control the interval between consecutive search requests. Rate Limit - 180 requests per 30 min. window (e.g. ~6 r/m, ~ 1 req / 10 sec.)
|
||||
|
||||
For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/supplier/twitter/search/stream/TwitterSearchSupplierProperties.java[TwitterSearchSupplierProperties].
|
||||
See link:src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java[TwitterConnectionProperties] and https://github.com/spring-cloud/spring-cloud-stream/blob/master/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/DefaultPollerProperties.java[DefaultPollerProperties]
|
||||
For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierProperties.java[TwitterSearchSupplierProperties].
|
||||
See `TwitterConnectionProperties` also.
|
||||
|
||||
### 1.3 Other usage
|
||||
|
||||
@@ -48,9 +50,11 @@ Users have to subscribe to this `Flux` and receive the data.
|
||||
|
||||
The default access level allows up to 400 track keywords, 5,000 follow user Ids and 25 0.1-360 degree location boxes.
|
||||
|
||||
To enable this supplier, the `twitter.stream.enabled` must be set to `true`.
|
||||
|
||||
### 2.1 Beans for injection
|
||||
|
||||
You can import the `TwitterStreamSupplierConfiguration` in the application and then inject the following bean.
|
||||
The `TwitterStreamSupplierConfiguration` auto-configuration provides the following bean:
|
||||
|
||||
`twitterStreamSupplier`
|
||||
|
||||
@@ -66,7 +70,7 @@ All configuration properties are prefixed with `twitter.stream`.
|
||||
There are also properties that need to be used with the prefix `twitter.connection`.
|
||||
|
||||
For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierProperties.java[TwitterStreamSupplierProperties].
|
||||
See link:src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java[TwitterConnectionProperties] also.
|
||||
See `TwitterConnectionProperties` also.
|
||||
|
||||
|
||||
### 2.3 Other usage
|
||||
@@ -75,18 +79,20 @@ See this https://github.com/spring-cloud/stream-applications/blob/master/applica
|
||||
|
||||
## 3. Twitter Direct Message Supplier
|
||||
|
||||
To enable this supplier, the `twitter.message.source.enabled` must be set to `true`.
|
||||
|
||||
Repeatedly retrieves the direct messages (both sent and received) within the last 30 days, sorted in reverse-chronological order.
|
||||
The relieved messages are cached (in a `MetadataStore` cache) to prevent duplications.
|
||||
By default an in-memory `SimpleMetadataStore` is used.
|
||||
By default, an in-memory `SimpleMetadataStore` is used.
|
||||
|
||||
The `twitter.message.source.count` controls the number or returned messages.
|
||||
|
||||
The `spring.cloud.stream.poller` properties control the message poll interval.
|
||||
The `spring.integration.poller` properties control the message poll interval.
|
||||
Must be aligned with used APIs rate limit
|
||||
|
||||
### 3.1 Beans for injection
|
||||
|
||||
You can import the `TwitterMessageSupplierConfiguration` in the application and then inject the following bean.
|
||||
The `TwitterMessageSupplierConfiguration` auto-configuration provides the following bean:
|
||||
|
||||
`twitterMessageSupplier`
|
||||
|
||||
@@ -101,10 +107,10 @@ Once injected, you can use the `get` method of the `Supplier` to invoke it.
|
||||
The configuration properties prefixed with `twitter.search`.
|
||||
There are also properties that need to be used with the prefix `twitter.connection`.
|
||||
|
||||
The `spring.cloud.stream.poller` properties control the interval between consecutive search requests. Rate Limit - 180 requests per 30 min. window (e.g. ~6 r/m, ~ 1 req / 10 sec.)
|
||||
The `spring.integration.poller` properties control the interval between consecutive search requests. Rate Limit - 180 requests per 30 min. window (e.g. ~6 r/m, ~ 1 req / 10 sec.)
|
||||
|
||||
For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierProperties.java[TwitterMessageSupplierProperties].
|
||||
See link:src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java[TwitterConnectionProperties] and https://github.com/spring-cloud/spring-cloud-stream/blob/master/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/DefaultPollerProperties.java[DefaultPollerProperties]
|
||||
See `TwitterConnectionProperties` also.
|
||||
|
||||
### 3.3 Other usage
|
||||
|
||||
@@ -112,15 +118,17 @@ See this https://github.com/spring-cloud/stream-applications/blob/master/applica
|
||||
|
||||
## 4. Twitter Friendships Supplier
|
||||
|
||||
To enable this supplier, the `twitter.friendships.source.enabled` must be set to `true`.
|
||||
|
||||
Returns a cursored collection of user objects either for the https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list[users following the specified user] (`followers`) or for https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list[every user the specified user is following] (`friends`).
|
||||
|
||||
The `twitter.friendships.source.type` property allow to select between both types.
|
||||
|
||||
TIP: Rate limit: 15 requests per 30 min window. ~ 1 req/ 2 min
|
||||
TIP: Rate limit: 15 requests per 30-min window. ~ 1 req/ 2 min
|
||||
|
||||
### 4.1 Beans for injection
|
||||
|
||||
You can import the `TwitterFriendshipsSupplierConfiguration` in the application and then inject one the following beans.
|
||||
The `TwitterFriendshipsSupplierConfiguration` auto-configuration provides the following beans:
|
||||
|
||||
- `followersSupplier` (only if `twitter.friendships.source.type=followers` ) - retrieves the https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list[users following the specified user] (`followers`)
|
||||
|
||||
@@ -137,7 +145,7 @@ encoded as JSON `Message` payloads. You need to inject this as `Supplier<Message
|
||||
The configuration properties prefixed with `twitter.friendships.source`.
|
||||
There are also properties that need to be used with the prefix `twitter.connection`.
|
||||
|
||||
The `spring.cloud.stream.poller` properties control the interval between consecutive search requests.
|
||||
The `spring.integration.poller` properties control the interval between consecutive search requests.
|
||||
|
||||
For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/TwitterFriendshipsSupplierProperties.java[TwitterFriendshipsSupplierProperties].
|
||||
See link:src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java[TwitterConnectionProperties] and https://github.com/spring-cloud/spring-cloud-stream/blob/master/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/config/DefaultPollerProperties.java[DefaultPollerProperties]
|
||||
See `TwitterConnectionProperties` also.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,27 +28,29 @@ import twitter4j.Twitter;
|
||||
import twitter4j.TwitterException;
|
||||
import twitter4j.User;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.Cursor;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.metadata.MetadataStore;
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* The auto-configuration for receiving Twitter friendship updates via supplier.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "twitter.friendships.source", name = "enabled")
|
||||
@EnableConfigurationProperties(TwitterFriendshipsSupplierProperties.class)
|
||||
@Import(TwitterConnectionConfiguration.class)
|
||||
@AutoConfiguration(after = TwitterConnectionConfiguration.class)
|
||||
public class TwitterFriendshipsSupplierConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TwitterFriendshipsSupplierConfiguration.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(TwitterFriendshipsSupplierConfiguration.class);
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@@ -66,6 +68,7 @@ public class TwitterFriendshipsSupplierConfiguration {
|
||||
@ConditionalOnProperty(name = "twitter.friendships.source.type", havingValue = "followers")
|
||||
public Supplier<List<User>> followersSupplier(TwitterFriendshipsSupplierProperties properties, Twitter twitter,
|
||||
Cursor cursorState) {
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
PagableResponseList<User> users;
|
||||
@@ -83,12 +86,12 @@ public class TwitterFriendshipsSupplierConfiguration {
|
||||
return users;
|
||||
}
|
||||
|
||||
logger.error(String.format("NULL users response for properties: %s and cursor: %s!", properties,
|
||||
LOGGER.error(String.format("NULL users response for properties: %s and cursor: %s!", properties,
|
||||
cursorState));
|
||||
cursorState.updateCursor(-1);
|
||||
}
|
||||
catch (TwitterException e) {
|
||||
logger.error("Twitter API error:", e);
|
||||
catch (TwitterException ex) {
|
||||
LOGGER.error("Twitter API error:", ex);
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
@@ -99,6 +102,7 @@ public class TwitterFriendshipsSupplierConfiguration {
|
||||
@ConditionalOnProperty(name = "twitter.friendships.source.type", havingValue = "friends")
|
||||
public Supplier<List<User>> friendsSupplier(TwitterFriendshipsSupplierProperties properties, Twitter twitter,
|
||||
Cursor cursorState) {
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
PagableResponseList<User> users;
|
||||
@@ -116,12 +120,12 @@ public class TwitterFriendshipsSupplierConfiguration {
|
||||
return users;
|
||||
}
|
||||
|
||||
logger.error(String.format("NULL users response for properties: %s and cursor: %s!", properties,
|
||||
LOGGER.error(String.format("NULL users response for properties: %s and cursor: %s!", properties,
|
||||
cursorState));
|
||||
cursorState.updateCursor(-1);
|
||||
}
|
||||
catch (TwitterException e) {
|
||||
logger.error("Twitter API error:", e);
|
||||
catch (TwitterException ex) {
|
||||
LOGGER.error("Twitter API error:", ex);
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
@@ -130,7 +134,7 @@ public class TwitterFriendshipsSupplierConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<List<User>, List<User>> userDeduplicate(MetadataStore metadataStore) {
|
||||
return users -> {
|
||||
return (users) -> {
|
||||
List<User> uniqueUsers = new ArrayList<>();
|
||||
for (User user : users) {
|
||||
if (metadataStore.get(user.getId() + "") == null) {
|
||||
@@ -145,6 +149,7 @@ public class TwitterFriendshipsSupplierConfiguration {
|
||||
@Bean
|
||||
public Supplier<Message<byte[]>> deduplicatedFriendsJsonSupplier(Function<List<User>, List<User>> userDeduplication,
|
||||
Supplier<List<User>> userRetriever, Function<Object, Message<byte[]>> managedJson) {
|
||||
|
||||
return () -> userDeduplication.andThen(managedJson).apply(userRetriever.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter supplier friendship updates receiving properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ConfigurationProperties("twitter.friendships.source")
|
||||
@@ -38,6 +40,11 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to enable Twitter friendship updates receiving.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Selects between followers or friends APIs.
|
||||
*/
|
||||
@@ -77,8 +84,16 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
*/
|
||||
private int pollInterval = 121000;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public FriendshipsRequestType getType() {
|
||||
return type;
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(FriendshipsRequestType type) {
|
||||
@@ -86,7 +101,7 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
}
|
||||
|
||||
public String getScreenName() {
|
||||
return screenName;
|
||||
return this.screenName;
|
||||
}
|
||||
|
||||
public void setScreenName(String screenName) {
|
||||
@@ -94,7 +109,7 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
public void setUserId(Long userId) {
|
||||
@@ -102,7 +117,7 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
@@ -110,7 +125,7 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
}
|
||||
|
||||
public boolean isSkipStatus() {
|
||||
return skipStatus;
|
||||
return this.skipStatus;
|
||||
}
|
||||
|
||||
public void setSkipStatus(boolean skipStatus) {
|
||||
@@ -118,7 +133,7 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
}
|
||||
|
||||
public boolean isIncludeUserEntities() {
|
||||
return includeUserEntities;
|
||||
return this.includeUserEntities;
|
||||
}
|
||||
|
||||
public void setIncludeUserEntities(boolean includeUserEntities) {
|
||||
@@ -126,7 +141,7 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
}
|
||||
|
||||
public int getPollInterval() {
|
||||
return pollInterval;
|
||||
return this.pollInterval;
|
||||
}
|
||||
|
||||
public void setPollInterval(int pollInterval) {
|
||||
@@ -140,9 +155,9 @@ public class TwitterFriendshipsSupplierProperties {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TwitterFriendshipsSourceProperties{" + "type=" + type + ", screenName='" + screenName + '\''
|
||||
+ ", userId=" + userId + ", count=" + count + ", skipStatus=" + skipStatus + ", includeUserEntities="
|
||||
+ includeUserEntities + ", pollInterval=" + pollInterval + '}';
|
||||
return "TwitterFriendshipsSourceProperties{" + "type=" + this.type + ", screenName='" + this.screenName + '\''
|
||||
+ ", userId=" + this.userId + ", count=" + this.count + ", skipStatus=" + this.skipStatus
|
||||
+ ", includeUserEntities=" + this.includeUserEntities + ", pollInterval=" + this.pollInterval + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter friendships updates supplier support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.supplier.twitter.friendships;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,20 +28,25 @@ import twitter4j.DirectMessageList;
|
||||
import twitter4j.Twitter;
|
||||
import twitter4j.TwitterException;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.metadata.MetadataStore;
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* The auto-configuration for receiving Twitter messages via supplier.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@EnableConfigurationProperties({ TwitterMessageSupplierProperties.class })
|
||||
@Import(TwitterConnectionConfiguration.class)
|
||||
@ConditionalOnProperty(prefix = "twitter.message.source", name = "enabled")
|
||||
@AutoConfiguration(after = TwitterConnectionConfiguration.class)
|
||||
@EnableConfigurationProperties(TwitterMessageSupplierProperties.class)
|
||||
public class TwitterMessageSupplierConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TwitterMessageSupplierConfiguration.class);
|
||||
@@ -61,9 +66,9 @@ public class TwitterMessageSupplierConfiguration {
|
||||
@Bean
|
||||
public Supplier<List<DirectMessage>> directMessagesSupplier(TwitterMessageSupplierProperties properties,
|
||||
Twitter twitter, MessageCursor cursorState) {
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
String cs = cursorState.getCursor();
|
||||
DirectMessageList messages = (cursorState.getCursor() == null)
|
||||
? twitter.getDirectMessages(properties.getCount())
|
||||
: twitter.getDirectMessages(properties.getCount(), cursorState.getCursor());
|
||||
@@ -77,8 +82,8 @@ public class TwitterMessageSupplierConfiguration {
|
||||
cursorState));
|
||||
cursorState.updateCursor(null);
|
||||
}
|
||||
catch (TwitterException e) {
|
||||
logger.error("Twitter API error:", e);
|
||||
catch (TwitterException ex) {
|
||||
logger.error("Twitter API error:", ex);
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
@@ -87,11 +92,12 @@ public class TwitterMessageSupplierConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<List<DirectMessage>, List<DirectMessage>> messageDeduplicate(MetadataStore metadataStore) {
|
||||
return messages -> {
|
||||
return (messages) -> {
|
||||
List<DirectMessage> uniqueMessages = new ArrayList<>();
|
||||
for (DirectMessage message : messages) {
|
||||
if (metadataStore.get(message.getId() + "") == null) {
|
||||
metadataStore.put(message.getId() + "", message.getCreatedAt() + "");
|
||||
long id = message.getId();
|
||||
if (metadataStore.get(id + "") == null) {
|
||||
metadataStore.put(id + "", message.getCreatedAt() + "");
|
||||
uniqueMessages.add(message);
|
||||
}
|
||||
}
|
||||
@@ -103,6 +109,7 @@ public class TwitterMessageSupplierConfiguration {
|
||||
public Supplier<Message<byte[]>> twitterMessageSupplier(
|
||||
Function<List<DirectMessage>, List<DirectMessage>> messageDeduplicate,
|
||||
Function<Object, Message<byte[]>> managedJson, Supplier<List<DirectMessage>> directMessagesSupplier) {
|
||||
|
||||
return () -> messageDeduplicate.andThen(managedJson).apply(directMessagesSupplier.get());
|
||||
}
|
||||
|
||||
@@ -111,7 +118,7 @@ public class TwitterMessageSupplierConfiguration {
|
||||
private String cursor = null;
|
||||
|
||||
public String getCursor() {
|
||||
return cursor;
|
||||
return this.cursor;
|
||||
}
|
||||
|
||||
public void updateCursor(String newCursor) {
|
||||
@@ -120,7 +127,7 @@ public class TwitterMessageSupplierConfiguration {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cursor{" + "cursor=" + cursor + '}';
|
||||
return "Cursor{" + "cursor=" + this.cursor + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,20 +22,36 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter supplier messages receiving properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@ConfigurationProperties("twitter.message.source")
|
||||
@Validated
|
||||
public class TwitterMessageSupplierProperties {
|
||||
|
||||
/**
|
||||
* Whether to enable Twitter message receiving.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Max number of events to be returned. 20 default. 50 max.
|
||||
*/
|
||||
@Max(50)
|
||||
private int count = 20;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter message supplier support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.supplier.twitter.message;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,24 +25,24 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* Searched tweets are ordered from top to bottom by their IDs. The higher the ID, more
|
||||
* recent the tweet is.
|
||||
*
|
||||
* <p>
|
||||
* The search goes backwards - from most recent to the oldest tweets and it uses the
|
||||
* sinceId and the maxId to retrieve only tweets with IDs in the [sinceId, maxId) range.
|
||||
* The -1 stands for unbounded sinceId or maxId.
|
||||
*
|
||||
* <p>
|
||||
* The first `pageCount` number requests are performed backwards, leaving the bottom
|
||||
* boundary (sinceId) unbounded and adjusting the upper boundary (maxId) to the lowest
|
||||
* tweet ID received. This ensures that no already processed tweets are returned.
|
||||
*
|
||||
* <p>
|
||||
* The pageCounter is used the to count the number of pages retrieved in the pageCount
|
||||
* range. Is starts from pageCount and goes backward until 0. On 0 the pageCounter is
|
||||
* reset back to pageCount.
|
||||
*
|
||||
* <p>
|
||||
* After performing pageCount number requests (e.g. pageCounter = 0), we start new
|
||||
* iteration of searches from the top, most recent tweets but now the bottom boundary
|
||||
* (sinceId) is adjusted to the max ID received so far. That means that only the newly
|
||||
* added tweets will be processed
|
||||
*
|
||||
* <p>
|
||||
* Search pagination with max_id and since_id:
|
||||
* https://developer.twitter.com/en/docs/tweets/timelines/guides/working-with-timelines.html
|
||||
*
|
||||
@@ -56,10 +56,9 @@ public class SearchPagination {
|
||||
public static final long UNBOUNDED = -1;
|
||||
|
||||
/**
|
||||
* Number of pages to search in history before start form the top again.
|
||||
*
|
||||
* Note that the search goes backwards - from most recent to the oldest tweets. (eg.
|
||||
* maxId == To max ID , sinceId == From min ID)
|
||||
* Number of pages to search in history before start form the top again. Note that the
|
||||
* search goes backwards - from most recent to the oldest tweets. (e.g. maxId == To
|
||||
* max ID , sinceId == From min ID)
|
||||
*/
|
||||
private final int pageCount;
|
||||
|
||||
@@ -86,7 +85,7 @@ public class SearchPagination {
|
||||
/**
|
||||
* When set it.
|
||||
*/
|
||||
boolean searchBackwardsUntilEmptyResponse = false;
|
||||
boolean searchBackwardsUntilEmptyResponse;
|
||||
|
||||
public SearchPagination(int pageCount, boolean searchBackwardsUntilEmptyResponse) {
|
||||
|
||||
@@ -101,38 +100,34 @@ public class SearchPagination {
|
||||
}
|
||||
|
||||
public long getSinceId() {
|
||||
return sinceId;
|
||||
return this.sinceId;
|
||||
}
|
||||
|
||||
public long getMaxId() {
|
||||
return maxId;
|
||||
return this.maxId;
|
||||
}
|
||||
|
||||
public long getPageMaxId() {
|
||||
return pageMaxId;
|
||||
return this.pageMaxId;
|
||||
}
|
||||
|
||||
public int getPageCounter() {
|
||||
return pageCounter;
|
||||
return this.pageCounter;
|
||||
}
|
||||
|
||||
public void update(List<Status> tweets) {
|
||||
tweets.stream().mapToLong(Status::getId).min().ifPresent((tweetsMinId) -> this.maxId = tweetsMinId - 1);
|
||||
|
||||
tweets.stream().mapToLong(t -> t.getId()).min().ifPresent(tweetsMinId -> {
|
||||
this.maxId = tweetsMinId - 1;
|
||||
});
|
||||
|
||||
tweets.stream().mapToLong(t -> t.getId()).max().ifPresent(tweetsMaxId -> {
|
||||
tweets.stream().mapToLong(Status::getId).max().ifPresent((tweetsMaxId) -> {
|
||||
Assert.isTrue(this.sinceId <= tweetsMaxId,
|
||||
String.format("MAX_ID (%s) must be bigger then current SINCE_ID(%s)", tweetsMaxId, this.sinceId));
|
||||
this.pageMaxId = Math.max(this.pageMaxId, tweetsMaxId);
|
||||
});
|
||||
|
||||
this.countDown(tweets.size());
|
||||
countDown(tweets.size());
|
||||
}
|
||||
|
||||
private void countDown(int responseSize) {
|
||||
|
||||
if (this.sinceId == UNBOUNDED) { // == first pass before reset
|
||||
if (this.pageCounter <= 0) {
|
||||
this.restartSearchFromMostRecent();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,10 +30,11 @@ import twitter4j.Twitter;
|
||||
import twitter4j.TwitterException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -45,12 +46,14 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Chris Bono
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@EnableConfigurationProperties({ TwitterSearchSupplierProperties.class })
|
||||
@Import(TwitterConnectionConfiguration.class)
|
||||
@ConditionalOnProperty(prefix = "twitter.search", name = "enabled")
|
||||
@EnableConfigurationProperties(TwitterSearchSupplierProperties.class)
|
||||
@AutoConfiguration(after = TwitterConnectionConfiguration.class)
|
||||
public class TwitterSearchSupplierConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TwitterSearchSupplierConfiguration.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(TwitterSearchSupplierConfiguration.class);
|
||||
|
||||
@Autowired
|
||||
private TwitterSearchSupplierProperties searchProperties;
|
||||
@@ -77,21 +80,21 @@ public class TwitterSearchSupplierConfiguration {
|
||||
|
||||
List<Status> tweets = result.getTweets();
|
||||
|
||||
logger.info(String.format("%s, size: %s", searchPage.status(), tweets.size()));
|
||||
LOGGER.info(String.format("%s, size: %s", searchPage.status(), tweets.size()));
|
||||
|
||||
searchPage.update(tweets);
|
||||
|
||||
return this.json.apply(tweets);
|
||||
}
|
||||
catch (TwitterException e) {
|
||||
logger.error("Twitter error", e);
|
||||
catch (TwitterException ex) {
|
||||
LOGGER.error("Twitter error", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private Query toQuery(TwitterSearchSupplierProperties searchProperties, SearchPagination pagination) {
|
||||
private static Query toQuery(TwitterSearchSupplierProperties searchProperties, SearchPagination pagination) {
|
||||
|
||||
Query query = new Query();
|
||||
if (searchProperties.getCount() > 0) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,12 +27,20 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter search supplier properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@ConfigurationProperties("twitter.search")
|
||||
@Validated
|
||||
public class TwitterSearchSupplierProperties {
|
||||
|
||||
/**
|
||||
* Whether to enable Twitter search supplier.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Search tweets by search query string.
|
||||
*/
|
||||
@@ -57,7 +65,7 @@ public class TwitterSearchSupplierProperties {
|
||||
|
||||
/**
|
||||
* Restricts searched tweets to the given language, given by an
|
||||
* http://en.wikipedia.org/wiki/ISO_639-1 .
|
||||
* http://en.wikipedia.org/wiki/ISO_639-1.
|
||||
*/
|
||||
private String lang = null;
|
||||
|
||||
@@ -73,7 +81,7 @@ public class TwitterSearchSupplierProperties {
|
||||
* given latitude/longitude, where the user's location is taken from their Twitter
|
||||
* profile. Should be formatted as
|
||||
*/
|
||||
private Geocode geocode = new Geocode();
|
||||
private final Geocode geocode = new Geocode();
|
||||
|
||||
/**
|
||||
* Specifies what type of search results you would prefer to receive. The current
|
||||
@@ -90,8 +98,16 @@ public class TwitterSearchSupplierProperties {
|
||||
*/
|
||||
private boolean restartFromMostRecentOnEmptyResponse = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
return this.query;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
@@ -99,7 +115,7 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public String getLang() {
|
||||
return lang;
|
||||
return this.lang;
|
||||
}
|
||||
|
||||
public void setLang(String lang) {
|
||||
@@ -107,7 +123,7 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
return this.page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
@@ -115,7 +131,7 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
@@ -123,7 +139,7 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public String getSince() {
|
||||
return since;
|
||||
return this.since;
|
||||
}
|
||||
|
||||
public void setSince(String since) {
|
||||
@@ -131,11 +147,11 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public Geocode getGeocode() {
|
||||
return geocode;
|
||||
return this.geocode;
|
||||
}
|
||||
|
||||
public Query.ResultType getResultType() {
|
||||
return resultType;
|
||||
return this.resultType;
|
||||
}
|
||||
|
||||
public void setResultType(Query.ResultType resultType) {
|
||||
@@ -143,7 +159,7 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public boolean isRestartFromMostRecentOnEmptyResponse() {
|
||||
return restartFromMostRecentOnEmptyResponse;
|
||||
return this.restartFromMostRecentOnEmptyResponse;
|
||||
}
|
||||
|
||||
public void setRestartFromMostRecentOnEmptyResponse(boolean restartFromMostRecentOnEmptyResponse) {
|
||||
@@ -168,7 +184,7 @@ public class TwitterSearchSupplierProperties {
|
||||
private double radius = -1;
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
return this.latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
@@ -176,7 +192,7 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
return this.longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
@@ -184,7 +200,7 @@ public class TwitterSearchSupplierProperties {
|
||||
}
|
||||
|
||||
public double getRadius() {
|
||||
return radius;
|
||||
return this.radius;
|
||||
}
|
||||
|
||||
public void setRadius(double radius) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter search supplier support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.supplier.twitter.status.search;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,11 +29,11 @@ import twitter4j.StatusDeletionNotice;
|
||||
import twitter4j.StatusListener;
|
||||
import twitter4j.TwitterStream;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.channel.FluxMessageChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
@@ -41,14 +41,17 @@ import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* The auto-configuration for real-time Twitter streaming API via supplier.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
|
||||
@EnableConfigurationProperties({ TwitterStreamSupplierProperties.class, TwitterConnectionProperties.class })
|
||||
@Import(TwitterConnectionConfiguration.class)
|
||||
@ConditionalOnProperty(prefix = "twitter.stream", name = "enabled")
|
||||
@EnableConfigurationProperties(TwitterStreamSupplierProperties.class)
|
||||
@AutoConfiguration(after = TwitterConnectionConfiguration.class)
|
||||
public class TwitterStreamSupplierConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TwitterStreamSupplierConfiguration.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(TwitterStreamSupplierConfiguration.class);
|
||||
|
||||
@Bean
|
||||
public FluxMessageChannel twitterStatusInputChannel() {
|
||||
@@ -63,23 +66,23 @@ public class TwitterStreamSupplierConfiguration {
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
logger.error("Status Error: ", e);
|
||||
LOGGER.error("Status Error: ", e);
|
||||
throw new RuntimeException("Status Error: ", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeletionNotice(StatusDeletionNotice arg) {
|
||||
logger.info("StatusDeletionNotice: " + arg);
|
||||
LOGGER.info("StatusDeletionNotice: " + arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrubGeo(long userId, long upToStatusId) {
|
||||
logger.info("onScrubGeo: " + userId + ", " + upToStatusId);
|
||||
LOGGER.info("onScrubGeo: " + userId + ", " + upToStatusId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStallWarning(StallWarning warning) {
|
||||
logger.warn("Stall Warning: " + warning);
|
||||
LOGGER.warn("Stall Warning: " + warning);
|
||||
throw new RuntimeException("Stall Warning: " + warning);
|
||||
}
|
||||
|
||||
@@ -93,15 +96,16 @@ public class TwitterStreamSupplierConfiguration {
|
||||
.build();
|
||||
twitterStatusInputChannel.send(message);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
logger.error("Status to JSON conversion error!", e);
|
||||
throw new RuntimeException("Status to JSON conversion error!", e);
|
||||
catch (JsonProcessingException ex) {
|
||||
String errorMessage = "Status to JSON conversion error!";
|
||||
LOGGER.error(errorMessage, ex);
|
||||
throw new RuntimeException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
|
||||
logger.warn("Track Limitation Notice: " + numberOfLimitedStatuses);
|
||||
LOGGER.warn("Track Limitation Notice: " + numberOfLimitedStatuses);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -114,38 +118,31 @@ public class TwitterStreamSupplierConfiguration {
|
||||
public Supplier<Flux<Message<?>>> twitterStreamSupplier(TwitterStream twitterStream,
|
||||
FluxMessageChannel twitterStatusInputChannel, TwitterStreamSupplierProperties streamProperties) {
|
||||
|
||||
return () -> Flux.from(twitterStatusInputChannel).doOnSubscribe(subscription -> {
|
||||
return () -> Flux.from(twitterStatusInputChannel).doOnSubscribe((subscription) -> {
|
||||
try {
|
||||
switch (streamProperties.getType()) {
|
||||
|
||||
case filter:
|
||||
case filter -> {
|
||||
twitterStream.filter(streamProperties.getFilter().toFilterQuery());
|
||||
return;
|
||||
|
||||
case sample:
|
||||
}
|
||||
case sample -> {
|
||||
twitterStream.sample();
|
||||
return;
|
||||
|
||||
case firehose:
|
||||
}
|
||||
case firehose -> {
|
||||
twitterStream.firehose(streamProperties.getFilter().getCount());
|
||||
return;
|
||||
|
||||
case link:
|
||||
}
|
||||
case link -> {
|
||||
twitterStream.links(streamProperties.getFilter().getCount());
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown stream type:" + streamProperties.getType());
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unknown stream type:" + streamProperties.getType());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.logger.error("Filter is not property set");
|
||||
catch (Exception ex) {
|
||||
LOGGER.error("Filter is not property set");
|
||||
}
|
||||
}).doAfterTerminate(() -> {
|
||||
this.logger.info("Proactive cancel for twitter stream");
|
||||
LOGGER.info("Proactive cancel for twitter stream");
|
||||
twitterStream.shutdown();
|
||||
}).doOnError(throwable -> {
|
||||
this.logger.error(throwable.getMessage(), throwable);
|
||||
});
|
||||
}).doOnError((throwable) -> LOGGER.error(throwable.getMessage(), throwable));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,8 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* The Twitter streaming supplier properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
@ConfigurationProperties("twitter.stream")
|
||||
@@ -70,16 +72,29 @@ public class TwitterStreamSupplierProperties {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to enable Twitter streaming supplier.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
private StreamType type = StreamType.sample;
|
||||
|
||||
private Filter filter = new Filter();
|
||||
private final Filter filter = new Filter();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Filter getFilter() {
|
||||
return filter;
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
public StreamType getType() {
|
||||
return type;
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void setType(StreamType type) {
|
||||
@@ -116,7 +131,7 @@ public class TwitterStreamSupplierProperties {
|
||||
* invalid: 52.38, 4.90, 51.51, -0.12. The first pair must be the SW corner of the
|
||||
* box
|
||||
*/
|
||||
private List<BoundingBox> locations = new ArrayList<>();
|
||||
private final List<BoundingBox> locations = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Specifies the tweets language of the stream.
|
||||
@@ -130,7 +145,7 @@ public class TwitterStreamSupplierProperties {
|
||||
private FilterLevel filterLevel = FilterLevel.all;
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
return this.count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
@@ -138,7 +153,7 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
public List<Long> getFollow() {
|
||||
return follow;
|
||||
return this.follow;
|
||||
}
|
||||
|
||||
public void setFollow(List<Long> follow) {
|
||||
@@ -146,7 +161,7 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
public List<String> getTrack() {
|
||||
return track;
|
||||
return this.track;
|
||||
}
|
||||
|
||||
public void setTrack(List<String> track) {
|
||||
@@ -154,7 +169,7 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
public List<String> getLanguage() {
|
||||
return language;
|
||||
return this.language;
|
||||
}
|
||||
|
||||
public void setLanguage(List<String> language) {
|
||||
@@ -162,11 +177,11 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
public List<BoundingBox> getLocations() {
|
||||
return locations;
|
||||
return this.locations;
|
||||
}
|
||||
|
||||
public FilterLevel getFilterLevel() {
|
||||
return filterLevel;
|
||||
return this.filterLevel;
|
||||
}
|
||||
|
||||
public void setFilterLevel(FilterLevel filterLevel) {
|
||||
@@ -192,7 +207,7 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.language)) {
|
||||
filterQuery.language(this.language.toArray(new String[this.language.size()]));
|
||||
filterQuery.language(this.language.toArray(new String[0]));
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.locations)) {
|
||||
@@ -216,7 +231,7 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return count > 0 || !CollectionUtils.isEmpty(this.track) || !CollectionUtils.isEmpty(this.follow)
|
||||
return this.count > 0 || !CollectionUtils.isEmpty(this.track) || !CollectionUtils.isEmpty(this.follow)
|
||||
|| !CollectionUtils.isEmpty(this.language) || this.filterLevel != FilterLevel.all;
|
||||
}
|
||||
|
||||
@@ -233,7 +248,7 @@ public class TwitterStreamSupplierProperties {
|
||||
private Geocode ne;
|
||||
|
||||
public Geocode getSw() {
|
||||
return sw;
|
||||
return this.sw;
|
||||
}
|
||||
|
||||
public void setSw(Geocode sw) {
|
||||
@@ -241,7 +256,7 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
public Geocode getNe() {
|
||||
return ne;
|
||||
return this.ne;
|
||||
}
|
||||
|
||||
public void setNe(Geocode ne) {
|
||||
@@ -263,7 +278,7 @@ public class TwitterStreamSupplierProperties {
|
||||
private double lon = -1;
|
||||
|
||||
public double getLat() {
|
||||
return lat;
|
||||
return this.lat;
|
||||
}
|
||||
|
||||
public void setLat(double lat) {
|
||||
@@ -271,7 +286,7 @@ public class TwitterStreamSupplierProperties {
|
||||
}
|
||||
|
||||
public double getLon() {
|
||||
return lon;
|
||||
return this.lon;
|
||||
}
|
||||
|
||||
public void setLon(double lon) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The Twitter streaming supplier support classes.
|
||||
*/
|
||||
package org.springframework.cloud.fn.supplier.twitter.status.stream;
|
||||
@@ -0,0 +1,4 @@
|
||||
org.springframework.cloud.fn.supplier.twitter.friendships.TwitterFriendshipsSupplierConfiguration
|
||||
org.springframework.cloud.fn.supplier.twitter.message.TwitterMessageSupplierConfiguration
|
||||
org.springframework.cloud.fn.supplier.twitter.status.search.TwitterSearchSupplierConfiguration
|
||||
org.springframework.cloud.fn.supplier.twitter.status.stream.TwitterStreamSupplierConfiguration
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.fn.supplier.twitter.status.search;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -156,6 +157,9 @@ public class SearchPaginationTests {
|
||||
|
||||
public static class MyStatus implements Status {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -6461195536943679985L;
|
||||
|
||||
private long id;
|
||||
|
||||
public MyStatus(long id) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.fn.supplier.twitter.status.stream;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -33,18 +32,15 @@ import reactor.test.StepVerifier;
|
||||
import twitter4j.conf.ConfigurationBuilder;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.fn.common.twitter.TwitterConnectionProperties;
|
||||
import org.springframework.cloud.fn.common.twitter.util.TwitterTestUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.util.TestSocketUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockserver.matchers.Times.exactly;
|
||||
@@ -62,10 +58,6 @@ import static org.mockserver.verify.VerificationTimes.once;
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
|
||||
public abstract class TwitterStreamSupplierTests {
|
||||
|
||||
private static final String MOCK_SERVER_IP = "127.0.0.1";
|
||||
|
||||
private static final Integer MOCK_SERVER_PORT = TestSocketUtils.findAvailableTcpPort();
|
||||
|
||||
private static ClientAndServer mockServer;
|
||||
|
||||
private static MockServerClient mockClient;
|
||||
@@ -81,10 +73,8 @@ public abstract class TwitterStreamSupplierTests {
|
||||
|
||||
@BeforeAll
|
||||
public static void startServer() {
|
||||
|
||||
mockServer = ClientAndServer.startClientAndServer(MOCK_SERVER_PORT);
|
||||
|
||||
mockClient = new MockServerClient(MOCK_SERVER_IP, MOCK_SERVER_PORT);
|
||||
mockServer = ClientAndServer.startClientAndServer();
|
||||
mockClient = new MockServerClient("localhost", mockServer.getPort());
|
||||
|
||||
streamFilterRequest = mockClientRecordRequest(request().withMethod("POST")
|
||||
.withPath("/stream/statuses/filter.json")
|
||||
@@ -112,12 +102,11 @@ public abstract class TwitterStreamSupplierTests {
|
||||
.respond(response().withStatusCode(200)
|
||||
.withHeaders(new Header("Content-Type", "application/json; charset=utf-8"),
|
||||
new Header("Cache-Control", "public, max-age=86400"))
|
||||
.withBody(TwitterTestUtils.asString("classpath:/response/stream_test_1.json"))
|
||||
.withDelay(TimeUnit.SECONDS, 10));
|
||||
.withBody(TwitterTestUtils.asString("classpath:/response/stream_test_1.json")));
|
||||
return request;
|
||||
}
|
||||
|
||||
@TestPropertySource(properties = { "twitter.stream.type=sample" })
|
||||
@TestPropertySource(properties = { "twitter.stream.enabled=true", "twitter.stream.type=sample" })
|
||||
public static class TwitterStreamSampleTests extends TwitterStreamSupplierTests {
|
||||
|
||||
@Test
|
||||
@@ -137,7 +126,8 @@ public abstract class TwitterStreamSupplierTests {
|
||||
|
||||
}
|
||||
|
||||
@TestPropertySource(properties = { "twitter.stream.type=filter", "twitter.stream.filter.track=Java,Python" })
|
||||
@TestPropertySource(properties = { "twitter.stream.enabled=true", "twitter.stream.type=filter",
|
||||
"twitter.stream.filter.track=Java,Python" })
|
||||
public static class TwitterStreamFilterTests extends TwitterStreamSupplierTests {
|
||||
|
||||
@Test
|
||||
@@ -157,7 +147,7 @@ public abstract class TwitterStreamSupplierTests {
|
||||
|
||||
}
|
||||
|
||||
@TestPropertySource(properties = { "twitter.stream.type=firehose" })
|
||||
@TestPropertySource(properties = { "twitter.stream.enabled=true", "twitter.stream.type=firehose" })
|
||||
public static class TwitterStreamFirehoseTests extends TwitterStreamSupplierTests {
|
||||
|
||||
@Test
|
||||
@@ -177,9 +167,7 @@ public abstract class TwitterStreamSupplierTests {
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@Import(TwitterStreamSupplierConfiguration.class)
|
||||
@SpringBootApplication
|
||||
public static class TwitterStreamSupplierTestApplication {
|
||||
|
||||
@Bean
|
||||
@@ -188,8 +176,7 @@ public abstract class TwitterStreamSupplierTests {
|
||||
Function<TwitterConnectionProperties, ConfigurationBuilder> toConfigurationBuilder) {
|
||||
|
||||
Function<TwitterConnectionProperties, ConfigurationBuilder> mockedConfiguration = toConfigurationBuilder
|
||||
.andThen(new TwitterTestUtils()
|
||||
.mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT)));
|
||||
.andThen(new TwitterTestUtils().mockTwitterUrls("http://localhost:" + mockServer.getPort()));
|
||||
|
||||
return mockedConfiguration.apply(properties).build();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user