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:
Artem Bilan
2024-01-04 16:14:16 -05:00
parent eb4ce0615a
commit de6a659ab1
36 changed files with 439 additions and 311 deletions

View File

@@ -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 + '}';
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
/**
* The Twitter4J connection support classes.
*/
package org.springframework.cloud.fn.common.twitter;

View File

@@ -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);
}
}

View File

@@ -0,0 +1,4 @@
/**
* The Twitter applications utility classes.
*/
package org.springframework.cloud.fn.common.twitter.util;

View File

@@ -0,0 +1 @@
org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration

View File

@@ -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`

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
/**
* The Twitter friendship support classes.
*/
package org.springframework.cloud.fn.consumer.twitter.friendship;

View File

@@ -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);
}
};
}

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
/**
* The Twitter messages support classes.
*/
package org.springframework.cloud.fn.consumer.twitter.message;

View File

@@ -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));
}
}

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
/**
* The Twitter status update support classes.
*/
package org.springframework.cloud.fn.consumer.twitter.status.update;

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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());
}

View File

@@ -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 + '}';
}
}

View File

@@ -0,0 +1,4 @@
/**
* The Twitter friendships updates supplier support classes.
*/
package org.springframework.cloud.fn.supplier.twitter.friendships;

View File

@@ -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 + '}';
}
}

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
/**
* The Twitter message supplier support classes.
*/
package org.springframework.cloud.fn.supplier.twitter.message;

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
/**
* The Twitter search supplier support classes.
*/
package org.springframework.cloud.fn.supplier.twitter.status.search;

View File

@@ -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));
}
}

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
/**
* The Twitter streaming supplier support classes.
*/
package org.springframework.cloud.fn.supplier.twitter.status.stream;

View File

@@ -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

View File

@@ -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) {

View File

@@ -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();
}