diff --git a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/Cursor.java b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/Cursor.java index b348c6d6..cd4c4d17 100644 --- a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/Cursor.java +++ b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/Cursor.java @@ -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 + '}'; } } diff --git a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionConfiguration.java b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionConfiguration.java index e9c48dbb..cf7c4e09 100644 --- a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionConfiguration.java +++ b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionConfiguration.java @@ -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 toConfigurationBuilder) { + return toConfigurationBuilder.apply(properties).build(); } @@ -66,7 +70,7 @@ public class TwitterConnectionConfiguration { @Bean public Function 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> 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 rawJsonExtractor() { - return response -> { - if (response instanceof List) { - List responses = (List) response; + return (response) -> { + if (response instanceof List responses) { List rawJsonList = new ArrayList<>(); for (Object object : responses) { rawJsonList.add(TwitterObjectFactory.getRawJSON(object)); @@ -119,7 +121,8 @@ public class TwitterConnectionConfiguration { @Bean public Function> managedJson(TwitterConnectionProperties properties, Function rawJsonExtractor, Function> 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); } } diff --git a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java index bc0fdf67..0d87712c 100644 --- a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java +++ b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/TwitterConnectionProperties.java @@ -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) { diff --git a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/package-info.java b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/package-info.java new file mode 100644 index 00000000..5db1da76 --- /dev/null +++ b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter4J connection support classes. + */ +package org.springframework.cloud.fn.common.twitter; diff --git a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/util/TwitterTestUtils.java b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/util/TwitterTestUtils.java index 85a929cc..e1251a97 100644 --- a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/util/TwitterTestUtils.java +++ b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/util/TwitterTestUtils.java @@ -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 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); } } diff --git a/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/util/package-info.java b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/util/package-info.java new file mode 100644 index 00000000..8bb56b22 --- /dev/null +++ b/common/spring-twitter-common/src/main/java/org/springframework/cloud/fn/common/twitter/util/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter applications utility classes. + */ +package org.springframework.cloud.fn.common.twitter.util; diff --git a/common/spring-twitter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/common/spring-twitter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..56034ad1 --- /dev/null +++ b/common/spring-twitter-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springframework.cloud.fn.common.twitter.TwitterConnectionConfiguration diff --git a/consumer/spring-twitter-consumer/README.adoc b/consumer/spring-twitter-consumer/README.adoc index 1f95e184..06ea7386 100644 --- a/consumer/spring-twitter-consumer/README.adoc +++ b/consumer/spring-twitter-consumer/README.adoc @@ -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 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> 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> friendshipConsumer` diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerConfiguration.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerConfiguration.java index 1e6189a8..f65157a5 100644 --- a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerConfiguration.java +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerConfiguration.java @@ -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> 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 { diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerProperties.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerProperties.java index 23925580..97079a6f 100644 --- a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerProperties.java +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/TwitterFriendshipsConsumerProperties.java @@ -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) { diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/package-info.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/package-info.java new file mode 100644 index 00000000..4f597103 --- /dev/null +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/friendship/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter friendship support classes. + */ +package org.springframework.cloud.fn.consumer.twitter.friendship; diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerConfiguration.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerConfiguration.java index 20c67c22..eb8be7b4 100644 --- a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerConfiguration.java +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerConfiguration.java @@ -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> 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); } }; } diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerProperties.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerProperties.java index 407bf3cf..c161dba6 100644 --- a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerProperties.java +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/TwitterMessageConsumerProperties.java @@ -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) { diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/package-info.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/package-info.java new file mode 100644 index 00000000..71a4b140 --- /dev/null +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/message/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter messages support classes. + */ +package org.springframework.cloud.fn.consumer.twitter.message; diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerConfiguration.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerConfiguration.java index 37b0b9cc..01a0dbdb 100644 --- a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerConfiguration.java +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerConfiguration.java @@ -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 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, 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> twitterStatusUpdateConsumer(Function, StatusUpdate> statusUpdateQuery, Consumer updateStatus) { - return message -> updateStatus.accept(statusUpdateQuery.apply(message)); + + return (message) -> updateStatus.accept(statusUpdateQuery.apply(message)); } } diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerProperties.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerProperties.java index 47a9b75a..9ad86847 100644 --- a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerProperties.java +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateConsumerProperties.java @@ -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) { diff --git a/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/package-info.java b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/package-info.java new file mode 100644 index 00000000..c5cfa518 --- /dev/null +++ b/consumer/spring-twitter-consumer/src/main/java/org/springframework/cloud/fn/consumer/twitter/status/update/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter status update support classes. + */ +package org.springframework.cloud.fn.consumer.twitter.status.update; diff --git a/consumer/spring-twitter-consumer/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/consumer/spring-twitter-consumer/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..b23838ff --- /dev/null +++ b/consumer/spring-twitter-consumer/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -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 diff --git a/consumer/spring-twitter-consumer/src/test/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateSinkFunctionConfigurationTests.java b/consumer/spring-twitter-consumer/src/test/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateSinkFunctionConfigurationTests.java index d24c70b4..bd0279f6 100644 --- a/consumer/spring-twitter-consumer/src/test/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateSinkFunctionConfigurationTests.java +++ b/consumer/spring-twitter-consumer/src/test/java/org/springframework/cloud/fn/consumer/twitter/status/update/TwitterUpdateSinkFunctionConfigurationTests.java @@ -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); } } diff --git a/supplier/spring-twitter-supplier/README.adoc b/supplier/spring-twitter-supplier/README.adoc index 9c795586..9de5dda5 100644 --- a/supplier/spring-twitter-supplier/README.adoc +++ b/supplier/spring-twitter-supplier/README.adoc @@ -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> followersSupplier(TwitterFriendshipsSupplierProperties properties, Twitter twitter, Cursor cursorState) { + return () -> { try { PagableResponseList 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> friendsSupplier(TwitterFriendshipsSupplierProperties properties, Twitter twitter, Cursor cursorState) { + return () -> { try { PagableResponseList 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> userDeduplicate(MetadataStore metadataStore) { - return users -> { + return (users) -> { List uniqueUsers = new ArrayList<>(); for (User user : users) { if (metadataStore.get(user.getId() + "") == null) { @@ -145,6 +149,7 @@ public class TwitterFriendshipsSupplierConfiguration { @Bean public Supplier> deduplicatedFriendsJsonSupplier(Function, List> userDeduplication, Supplier> userRetriever, Function> managedJson) { + return () -> userDeduplication.andThen(managedJson).apply(userRetriever.get()); } diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/TwitterFriendshipsSupplierProperties.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/TwitterFriendshipsSupplierProperties.java index 9fac4a83..82312de9 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/TwitterFriendshipsSupplierProperties.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/TwitterFriendshipsSupplierProperties.java @@ -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 + '}'; } } diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/package-info.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/package-info.java new file mode 100644 index 00000000..2f5b8117 --- /dev/null +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/friendships/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter friendships updates supplier support classes. + */ +package org.springframework.cloud.fn.supplier.twitter.friendships; diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierConfiguration.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierConfiguration.java index 62a4d464..9e0b7bc8 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierConfiguration.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierConfiguration.java @@ -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> 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> messageDeduplicate(MetadataStore metadataStore) { - return messages -> { + return (messages) -> { List 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> twitterMessageSupplier( Function, List> messageDeduplicate, Function> managedJson, Supplier> 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 + '}'; } } diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierProperties.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierProperties.java index 50e7ab09..c32a2750 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierProperties.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/TwitterMessageSupplierProperties.java @@ -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) { diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/package-info.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/package-info.java new file mode 100644 index 00000000..ba930af1 --- /dev/null +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/message/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter message supplier support classes. + */ +package org.springframework.cloud.fn.supplier.twitter.message; diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPagination.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPagination.java index a5b68079..2b95a18c 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPagination.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPagination.java @@ -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. - * + *

* 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. - * + *

* 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. - * + *

* 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. - * + *

* 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 - * + *

* 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 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(); diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierConfiguration.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierConfiguration.java index e153cbb1..b01f18ec 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierConfiguration.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierConfiguration.java @@ -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 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) { diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierProperties.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierProperties.java index 76e9509c..fde3ad63 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierProperties.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/TwitterSearchSupplierProperties.java @@ -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) { diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/package-info.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/package-info.java new file mode 100644 index 00000000..e5c82563 --- /dev/null +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/search/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter search supplier support classes. + */ +package org.springframework.cloud.fn.supplier.twitter.status.search; diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierConfiguration.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierConfiguration.java index 2eb2f9e1..330dd291 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierConfiguration.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierConfiguration.java @@ -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>> 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)); } } diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierProperties.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierProperties.java index 51dfaa9c..fc61483d 100644 --- a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierProperties.java +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierProperties.java @@ -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 locations = new ArrayList<>(); + private final List 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 getFollow() { - return follow; + return this.follow; } public void setFollow(List follow) { @@ -146,7 +161,7 @@ public class TwitterStreamSupplierProperties { } public List getTrack() { - return track; + return this.track; } public void setTrack(List track) { @@ -154,7 +169,7 @@ public class TwitterStreamSupplierProperties { } public List getLanguage() { - return language; + return this.language; } public void setLanguage(List language) { @@ -162,11 +177,11 @@ public class TwitterStreamSupplierProperties { } public List 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) { diff --git a/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/package-info.java b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/package-info.java new file mode 100644 index 00000000..cccbd5d9 --- /dev/null +++ b/supplier/spring-twitter-supplier/src/main/java/org/springframework/cloud/fn/supplier/twitter/status/stream/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter streaming supplier support classes. + */ +package org.springframework.cloud.fn.supplier.twitter.status.stream; diff --git a/supplier/spring-twitter-supplier/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/supplier/spring-twitter-supplier/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..e5556881 --- /dev/null +++ b/supplier/spring-twitter-supplier/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -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 diff --git a/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPaginationTests.java b/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPaginationTests.java index a12272d2..27f07dcf 100644 --- a/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPaginationTests.java +++ b/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/search/SearchPaginationTests.java @@ -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) { diff --git a/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierTests.java b/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierTests.java index 9f5a2249..8b969ddf 100644 --- a/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierTests.java +++ b/supplier/spring-twitter-supplier/src/test/java/org/springframework/cloud/fn/supplier/twitter/status/stream/TwitterStreamSupplierTests.java @@ -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 toConfigurationBuilder) { Function 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(); }