From e2f721b69f519b2da8b37d7e937e10629fe0a91c Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Wed, 10 Jan 2024 11:21:21 -0500 Subject: [PATCH] Make spring-twitter-function as auto-config * Fix Checkstyle violations in this module * Make all the Twitter function auto-configurations as conditional on their specific properties to avoid extra beans not expected in the target application * Fix README respectively --- function/spring-twitter-function/README.adoc | 46 ++++++++----------- .../geo/TwitterGeoFunctionConfiguration.java | 30 ++++++------ .../geo/TwitterGeoFunctionProperties.java | 37 +++++++-------- .../cloud/fn/twitter/geo/package-info.java | 4 ++ .../TwitterTrendFunctionConfiguration.java | 27 ++++++----- .../trend/TwitterTrendFunctionProperties.java | 38 +++++++-------- .../cloud/fn/twitter/trend/package-info.java | 4 ++ .../TwitterUsersFunctionConfiguration.java | 34 ++++++-------- .../users/TwitterUsersFunctionProperties.java | 37 +++++++-------- .../cloud/fn/twitter/users/package-info.java | 4 ++ ...ot.autoconfigure.AutoConfiguration.imports | 3 ++ ...Test.java => TwitterGeoFunctionTests.java} | 38 +++++++-------- .../trend/TwitterTrendFunctionTests.java | 16 +++---- .../users/TwitterUsersFunctionTests.java | 21 ++++----- 14 files changed, 164 insertions(+), 175 deletions(-) create mode 100644 function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/package-info.java create mode 100644 function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/package-info.java create mode 100644 function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/package-info.java create mode 100644 function/spring-twitter-function/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/geo/{TwitterGeoFunctionTest.java => TwitterGeoFunctionTests.java} (87%) diff --git a/function/spring-twitter-function/README.adoc b/function/spring-twitter-function/README.adoc index 1427a796..26cd6c56 100644 --- a/function/spring-twitter-function/README.adoc +++ b/function/spring-twitter-function/README.adoc @@ -2,34 +2,31 @@ This module provides couple of twitter functions that can be reused and composed in other applications. -To use those functions add the following dependency to your POM: +This module exposes auto-configurations for the following beans: -[source,XML] ----- - - org.springframework.cloud.fn - twitter-function - ${java-functions.version} - ----- +* `twitterTrendFunction` +* `twitterUsersFunction` +* `twitterGeoFunction` + +Each of them are conditional by specific configuration properties. ## 1. Twitter Trend Function -Functions can return either Trends topics or the Locations of the trending topics. The `twitter.trend.trend-query-type` property allows choosing between both types. +Function can return either Trends topics or the Locations of the trending topics. +The `twitter.trend.trend-query-type` property allows choosing between both types. +This property is required to enable the function auto-configuration. -* Trends - `twitter.trend.trend-query-type` is set to `trend`. Leverages the https://developer.twitter.com/en/docs/trends/trends-for-location/api-reference/get-trends-place[Trends API] to return the https://help.twitter.com/en/using-twitter/twitter-trending-faqs[trending topics] near a specific latitude, longitude location. +* Trends - `twitter.trend.trend-query-type` is set to `trend`. +Leverages the https://developer.twitter.com/en/docs/trends/trends-for-location/api-reference/get-trends-place[Trends API] to return the https://help.twitter.com/en/using-twitter/twitter-trending-faqs[trending topics] near a specific latitude, longitude location. -* Trend Locations - the `twitter.trend.trend-query-type` is set `trendLocation`. Retrieve a full or nearby locations list of trending topics by location. If the `latitude`, `longitude` parameters are NOT provided the processor performs the https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-available[Trends Available API] and returns the locations that Twitter has trending topic information for. +* Trend Locations - the `twitter.trend.trend-query-type` is set `trendLocation`. +Retrieve a full or nearby locations list of trending topics by location. If the `latitude`, `longitude` parameters are NOT provided the processor performs the https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-available[Trends Available API] and returns the locations that Twitter has trending topic information for. If the `latitude`, `longitude` parameters are provided the processor performs the https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-closest[Trends Closest API] and returns the locations that Twitter has trending topic information for, closest to a specified location. Response is an array of `locations` that encode the location's WOEID and some other human-readable information such as a canonical name and country the location belongs in. ### 1.1 Beans for injection -You can import the `TwitterTrendFunctionConfiguration` in a Spring Boot application and then inject the following bean. - -`filterFunction` - You can use `Function, Message> twitterTrendFunction` as a qualifier when injecting. Once injected, you can use the `apply` method of the `Function` to invoke it and get the result. @@ -45,12 +42,11 @@ See this link:src/test/java/org/springframework/cloud/fn/twitter/trend/TwitterTr ### 1.4 Other usage -Leveraging the Spring Cloud Function composability, you can compose the Trend function in your boot app like this: +Leveraging the Spring Cloud Function "composability", you can compose the Trend function in your boot app like this: [source,Java] ---- @SpringBootApplication -@Import(TwitterTrendFunctionConfiguration.class ) public class MyTwitterTrendBootApp { public static void main(String[] args) { @@ -79,11 +75,9 @@ Some parameters in this method are only required based on the existence of other NOTE: Limits: 15 requests / 15-min window (user auth). -### 2.1 Beans for injection - -You can import the `TwitterGeoFunctionConfiguration` in a Spring Boot application and then inject the following bean. +This function auto-configuration is conditional on `twitter.geo.search.ip != null || (twitter.geo.location.lat != null && twitter.geo.location.lon != null)` -`filterFunction` +### 2.1 Beans for injection You can use `Function, Message> twitterGeoFunction` as a qualifier when injecting. Once injected, you can use the `apply` method of the `Function` to invoke it and get the result. @@ -96,7 +90,7 @@ For more information on the various options available, please see link:../twitte ### 2.3 Tests -See this link:src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTest.java[test suite] for examples of how this function is used. +See this link:src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTests.java[test suite] for examples of how this function is used. ### 2.4 Other usage @@ -105,7 +99,6 @@ Leveraging the Spring Cloud Function composability, you can compose the Geo func [source,Java] ---- @SpringBootApplication -@Import(TwitterGeoFunctionConfiguration.class ) public class MyTwitterGeoProcessorBootApp { public static void main(String[] args) { @@ -122,6 +115,7 @@ Uses SpEL expressions to compute the query parameters from the input message. Use the single quoted literals to set static values (e.g. user-id: '6666, 9999, 10000'). Use `twitter.users.type` property allow to select the query approaches. +This property is required to trigger the `twitterUsersFunction` auto-configuration. * https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup[Users Lookup API] - Returns fully-hydrated user objects for up to 100 users per request, as specified by comma-separated values passed to the `userId` and/or `screenName` parameters. Rate limits: (300 requests / 15-min window) * https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-search[Users Search API] - Relevance-based search interface to public user accounts on Twitter. @@ -129,10 +123,6 @@ Querying by topical interest, full name, company name, location, or other criter ### 3.1 Beans for injection -You can import the `TwitterUsersFunctionConfiguration` in a Spring Boot application and then inject the following bean. - -`filterFunction` - You can use `Function, Message> twitterUsersFunction` as a qualifier when injecting. Once injected, you can use the `apply` method of the `Function` to invoke it and get the result. diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionConfiguration.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionConfiguration.java index 7529aa6a..0664b6e6 100644 --- a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionConfiguration.java +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionConfiguration.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,27 +27,29 @@ import twitter4j.Twitter; import twitter4j.TwitterException; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 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.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.messaging.Message; /** + * Auto-configuration for Twitter Geo function. + * * @author Christian Tzolov */ -@Configuration +@ConditionalOnExpression("environment['twitter.geo.search.ip'] != null || (environment['twitter.geo.location.lat'] != null && environment['twitter.geo.location.lon'] != null)") +@AutoConfiguration(after = TwitterConnectionConfiguration.class) @EnableConfigurationProperties(TwitterGeoFunctionProperties.class) -@Import(TwitterConnectionConfiguration.class) public class TwitterGeoFunctionConfiguration { - private static final Log logger = LogFactory.getLog(TwitterGeoFunctionConfiguration.class); + private static final Log LOGGER = LogFactory.getLog(TwitterGeoFunctionConfiguration.class); @Bean public Function, GeoQuery> messageToGeoQueryFunction(TwitterGeoFunctionProperties geoProperties) { - return message -> { + return (message) -> { String ip = null; if (geoProperties.getSearch().getIp() != null) { ip = geoProperties.getSearch().getIp().getValue(message, String.class); @@ -76,12 +78,12 @@ public Function, GeoQuery> messageToGeoQueryFunction(TwitterGeoFuncti @Bean @ConditionalOnProperty(name = "twitter.geo.search.type", havingValue = "search", matchIfMissing = true) public Function> twitterSearchPlacesFunction(Twitter twitter) { - return geoQuery -> { + return (geoQuery) -> { try { return twitter.searchPlaces(geoQuery); } - catch (TwitterException e) { - logger.error("Places Search failed!", e); + catch (TwitterException ex) { + LOGGER.error("Places Search failed!", ex); } return null; }; @@ -90,12 +92,12 @@ public Function> twitterSearchPlacesFunction(Twitter twitt @Bean @ConditionalOnProperty(name = "twitter.geo.search.type", havingValue = "reverse") public Function> twitterReverseGeocodeFunction(Twitter twitter) { - return geoQuery -> { + return (geoQuery) -> { try { return twitter.reverseGeoCode(geoQuery); } - catch (TwitterException e) { - logger.error("Reverse Geocode failed!", e); + catch (TwitterException ex) { + LOGGER.error("Reverse Geocode failed!", ex); } return null; }; @@ -105,7 +107,7 @@ public Function> twitterReverseGeocodeFunction(Twitter twi public Function, Message> twitterGeoFunction(Function, GeoQuery> toGeoQuery, Function> places, Function> managedJson) { - return toGeoQuery.andThen(places).andThen(managedJson)::apply; + return toGeoQuery.andThen(places).andThen(managedJson); } } diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionProperties.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionProperties.java index cf316d27..1c5ff0a1 100644 --- a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionProperties.java +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionProperties.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,18 +21,17 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.validation.annotation.Validated; /** + * Configuration properties for Twitter Geo function. + * * @author Christian Tzolov */ @ConfigurationProperties("twitter.geo") @Validated public class TwitterGeoFunctionProperties { - private static final Expression DEFAULT_EXPRESSION = new SpelExpressionParser().parseExpression("payload"); - public enum GeoType { /** Geo retrieval type. */ @@ -51,9 +50,6 @@ public enum GeoType { */ private Search search = new Search(); - /** - * - */ private Location location = new Location(); /** @@ -68,7 +64,7 @@ public enum GeoType { * in meters, but it can also take a string that is suffixed with ft to specify feet. * If this is not passed in, then it is assumed to be 0m. If coming from a device, in * practice, this value is whatever accuracy the device has measuring its location - * (whether it be coming from a GPS, WiFi triangulation, etc.). + * (whether it be coming from a GPS, Wi-Fi triangulation, etc.). */ private String accuracy = null; @@ -79,7 +75,7 @@ public enum GeoType { private String granularity = null; public Search getSearch() { - return search; + return this.search; } public void setSearch(Search search) { @@ -87,7 +83,7 @@ public void setSearch(Search search) { } public GeoType getType() { - return type; + return this.type; } public void setType(GeoType type) { @@ -95,7 +91,7 @@ public void setType(GeoType type) { } public Location getLocation() { - return location; + return this.location; } public void setLocation(Location location) { @@ -103,7 +99,7 @@ public void setLocation(Location location) { } public int getMaxResults() { - return maxResults; + return this.maxResults; } public void setMaxResults(int maxResults) { @@ -111,7 +107,7 @@ public void setMaxResults(int maxResults) { } public String getAccuracy() { - return accuracy; + return this.accuracy; } public void setAccuracy(String accuracy) { @@ -119,7 +115,7 @@ public void setAccuracy(String accuracy) { } public String getGranularity() { - return granularity; + return this.granularity; } public void setGranularity(String granularity) { @@ -128,13 +124,12 @@ public void setGranularity(String granularity) { @AssertTrue(message = "Either the IP or the Location must be set") public boolean isAtLeastOne() { - return this.getSearch().getIp() == null - ^ (this.getLocation().getLat() == null && this.getLocation().getLon() == null); + return getSearch().getIp() == null ^ (getLocation().getLat() == null && getLocation().getLon() == null); } @AssertTrue(message = "The IP parameter is applicable only for 'Search' GeoType") public boolean isIpUsedWithSearchGeoType() { - if (this.getSearch().getIp() != null) { + if (getSearch().getIp() != null) { return this.type == GeoType.search; } return true; @@ -154,7 +149,7 @@ public static class Search { private Expression query = null; public Expression getIp() { - return ip; + return this.ip; } public void setIp(Expression ip) { @@ -162,7 +157,7 @@ public void setIp(Expression ip) { } public Expression getQuery() { - return query; + return this.query; } public void setQuery(Expression query) { @@ -184,7 +179,7 @@ public static class Location { private Expression lon; public Expression getLat() { - return lat; + return this.lat; } public void setLat(Expression lat) { @@ -192,7 +187,7 @@ public void setLat(Expression lat) { } public Expression getLon() { - return lon; + return this.lon; } public void setLon(Expression lon) { diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/package-info.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/package-info.java new file mode 100644 index 00000000..4e945ae5 --- /dev/null +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/geo/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter Geo function auto-configuration. + */ +package org.springframework.cloud.fn.twitter.geo; diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionConfiguration.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionConfiguration.java index 236e85c6..6f2f5705 100644 --- a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionConfiguration.java +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionConfiguration.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,32 +27,34 @@ import twitter4j.Twitter; import twitter4j.TwitterException; +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.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.messaging.Message; /** + * Auto-configuration for Twitter Trend function. + * * @author Christian Tzolov */ -@Configuration +@ConditionalOnProperty(prefix = "twitter.trend", name = "trend-query-type") +@AutoConfiguration(after = TwitterConnectionConfiguration.class) @EnableConfigurationProperties(TwitterTrendFunctionProperties.class) -@Import(TwitterConnectionConfiguration.class) public class TwitterTrendFunctionConfiguration { - private static final Log logger = LogFactory.getLog(TwitterTrendFunctionConfiguration.class); + private static final Log LOGGER = LogFactory.getLog(TwitterTrendFunctionConfiguration.class); @Bean public Function, Trends> trend(TwitterTrendFunctionProperties properties, Twitter twitter) { - return message -> { + return (message) -> { try { int woeid = properties.getLocationId().getValue(message, int.class); return twitter.getPlaceTrends(woeid); } - catch (TwitterException e) { - logger.error("Twitter API error!", e); + catch (TwitterException ex) { + LOGGER.error("Twitter API error!", ex); } return null; }; @@ -61,7 +63,8 @@ public Function, Trends> trend(TwitterTrendFunctionProperties propert @Bean public Function, List> closestOrAvailableTrends(TwitterTrendFunctionProperties properties, Twitter twitter) { - return message -> { + + return (message) -> { try { if (properties.getClosest().getLat() != null && properties.getClosest().getLon() != null) { double lat = properties.getClosest().getLat().getValue(message, double.class); @@ -72,8 +75,8 @@ public Function, List> closestOrAvailableTrends(TwitterTren return twitter.getAvailableTrends(); } } - catch (TwitterException e) { - logger.error("Twitter API error!", e); + catch (TwitterException ex) { + LOGGER.error("Twitter API error!", ex); } return null; }; diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionProperties.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionProperties.java index 63cadcd0..5f944c92 100644 --- a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionProperties.java +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionProperties.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. @@ -20,19 +20,22 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.messaging.Message; import org.springframework.validation.annotation.Validated; /** + * Configuration properties for Twitter Trend function. + * * @author Christian Tzolov */ @ConfigurationProperties("twitter.trend") @Validated public class TwitterTrendFunctionProperties { - private static final Expression DEFAULT_EXPRESSION = new SpelExpressionParser().parseExpression("payload"); + private static final Expression DEFAULT_EXPRESSION = new FunctionExpression>(Message::getPayload); - enum TrendQueryType { + public enum TrendQueryType { /** Retrieve trending places. */ trend, @@ -41,10 +44,10 @@ enum TrendQueryType { } - private TrendQueryType trendQueryType = TrendQueryType.trend; + private TrendQueryType trendQueryType; public TrendQueryType getTrendQueryType() { - return trendQueryType; + return this.trendQueryType; } public void setTrendQueryType(TrendQueryType trendQueryType) { @@ -59,40 +62,39 @@ public void setTrendQueryType(TrendQueryType trendQueryType) { private Expression locationId = DEFAULT_EXPRESSION; public Expression getLocationId() { - return locationId; + return this.locationId; } public void setLocationId(Expression locationId) { this.locationId = locationId; } - /** - * - */ - private Closest closest = new Closest(); + private final Closest closest = new Closest(); public Closest getClosest() { - return closest; + return this.closest; } public static class Closest { /** * If provided with a long parameter the available trend locations will be sorted - * by distance, nearest to furthest, to the co-ordinate pair. The valid ranges for - * longitude is -180.0 to +180.0 (West is negative, East is positive) inclusive. + * by distance, nearest to the furthest, to the co-ordinate pair. The valid ranges + * for longitude is -180.0 to +180.0 (West is negative, East is positive) + * inclusive. */ private Expression lat; /** * If provided with a lat parameter the available trend locations will be sorted - * by distance, nearest to furthest, to the co-ordinate pair. The valid ranges for - * longitude is -180.0 to +180.0 (West is negative, East is positive) inclusive. + * by distance, nearest to the furthest, to the co-ordinate pair. The valid ranges + * for longitude is -180.0 to +180.0 (West is negative, East is positive) + * inclusive. */ private Expression lon; public Expression getLat() { - return lat; + return this.lat; } public void setLat(Expression lat) { @@ -100,7 +102,7 @@ public void setLat(Expression lat) { } public Expression getLon() { - return lon; + return this.lon; } public void setLon(Expression lon) { diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/package-info.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/package-info.java new file mode 100644 index 00000000..9ab3f542 --- /dev/null +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/trend/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter Trend function auto-configuration. + */ +package org.springframework.cloud.fn.twitter.trend; diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionConfiguration.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionConfiguration.java index 7dd4ed09..634a683f 100644 --- a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionConfiguration.java +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionConfiguration.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,41 +21,40 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import twitter4j.ResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.User; +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.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.messaging.Message; /** + * Auto-configuration for Twitter Users function. + * * @author Christian Tzolov */ -@Configuration +@ConditionalOnProperty(prefix = "twitter.users", name = "type") +@AutoConfiguration(after = TwitterConnectionConfiguration.class) @EnableConfigurationProperties(TwitterUsersFunctionProperties.class) -@Import(TwitterConnectionConfiguration.class) public class TwitterUsersFunctionConfiguration { - private static final Log logger = LogFactory.getLog(TwitterUsersFunctionConfiguration.class); + private static final Log LOGGER = LogFactory.getLog(TwitterUsersFunctionConfiguration.class); @Bean @ConditionalOnProperty(name = "twitter.users.type", havingValue = "search") public Function, List> userSearch(Twitter twitter, TwitterUsersFunctionProperties properties) { - return message -> { + return (message) -> { String query = properties.getSearch().getQuery().getValue(message, String.class); try { - ResponseList users = twitter.searchUsers(query, properties.getSearch().getPage()); - return users; + return twitter.searchUsers(query, properties.getSearch().getPage()); } - catch (TwitterException e) { - logger.error("Twitter API error!", e); + catch (TwitterException ex) { + LOGGER.error("Twitter API error!", ex); } return null; }; @@ -65,7 +64,7 @@ public Function, List> userSearch(Twitter twitter, TwitterUsers @ConditionalOnProperty(name = "twitter.users.type", havingValue = "lookup") public Function, List> userLookup(Twitter twitter, TwitterUsersFunctionProperties properties) { - return message -> { + return (message) -> { try { TwitterUsersFunctionProperties.Lookup lookup = properties.getLookup(); @@ -78,20 +77,17 @@ else if (lookup.getUserId() != null) { return twitter.lookupUsers(ids); } } - catch (TwitterException e) { - logger.error("Twitter API error!", e); + catch (TwitterException ex) { + LOGGER.error("Twitter API error!", ex); } return null; }; } @Bean - /** - * queryUsers - depends on the `twitter.users.type` property is either userSearch or - * userLookup. managedJson - converts Users into JSON message payload. - */ public Function, Message> twitterUsersFunction(Function, List> queryUsers, Function> managedJson) { + return queryUsers.andThen(managedJson); } diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionProperties.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionProperties.java index 75243026..fd865dc2 100644 --- a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionProperties.java +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionProperties.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,21 +17,23 @@ package org.springframework.cloud.fn.twitter.users; import jakarta.validation.constraints.AssertTrue; -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.FunctionExpression; +import org.springframework.messaging.Message; import org.springframework.validation.annotation.Validated; /** + * Configuration properties for Twitter Users function. + * * @author Christian Tzolov */ @ConfigurationProperties("twitter.users") @Validated public class TwitterUsersFunctionProperties { - private static final Expression DEFAULT_EXPRESSION = new SpelExpressionParser().parseExpression("payload"); + private static final Expression DEFAULT_EXPRESSION = new FunctionExpression>(Message::getPayload); public enum UserQueryType { @@ -43,8 +45,7 @@ public enum UserQueryType { /** * Perform search or lookup type of search. */ - @NotNull - private UserQueryType type = UserQueryType.search; + private UserQueryType type; /** * Returns fully-hydrated user objects for specified by comma-separated values passed @@ -53,13 +54,13 @@ public enum UserQueryType { private final Lookup lookup = new Lookup(); /** - * relevance-based search interface for querying by topical interest, full name, + * Relevance-based search interface for querying by topical interest, full name, * company name, location, or other criteria. */ private final Search search = new Search(); public UserQueryType getType() { - return type; + return this.type; } public void setType(UserQueryType type) { @@ -67,20 +68,20 @@ public void setType(UserQueryType type) { } public Lookup getLookup() { - return lookup; + return this.lookup; } public Search getSearch() { - return search; + return this.search; } @AssertTrue(message = "Per query type validate the required parameters") public boolean checkParametersPerType() { - if (this.getType() == UserQueryType.lookup) { - return (this.getLookup().getScreenName() != null) || (this.getLookup().getUserId() != null); + if (getType() == UserQueryType.lookup) { + return (getLookup().getScreenName() != null) || (getLookup().getUserId() != null); } - else if (this.getType() == UserQueryType.search) { - return (this.getSearch().getQuery() != null); + else if (getType() == UserQueryType.search) { + return (getSearch().getQuery() != null); } return false; @@ -102,7 +103,7 @@ public static class Lookup { private Expression screenName; public Expression getUserId() { - return userId; + return this.userId; } public void setUserId(Expression userId) { @@ -110,7 +111,7 @@ public void setUserId(Expression userId) { } public Expression getScreenName() { - return screenName; + return this.screenName; } public void setScreenName(Expression screenName) { @@ -132,7 +133,7 @@ public static class Search { private int page = 3; public Expression getQuery() { - return query; + return this.query; } public void setQuery(Expression query) { @@ -140,7 +141,7 @@ public void setQuery(Expression query) { } public int getPage() { - return page; + return this.page; } public void setPage(int page) { diff --git a/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/package-info.java b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/package-info.java new file mode 100644 index 00000000..9c1b358f --- /dev/null +++ b/function/spring-twitter-function/src/main/java/org/springframework/cloud/fn/twitter/users/package-info.java @@ -0,0 +1,4 @@ +/** + * The Twitter Users function auto-configuration. + */ +package org.springframework.cloud.fn.twitter.users; diff --git a/function/spring-twitter-function/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/function/spring-twitter-function/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..72bc7b5d --- /dev/null +++ b/function/spring-twitter-function/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +org.springframework.cloud.fn.twitter.geo.TwitterGeoFunctionConfiguration +org.springframework.cloud.fn.twitter.trend.TwitterTrendFunctionConfiguration +org.springframework.cloud.fn.twitter.users.TwitterUsersFunctionConfiguration diff --git a/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTest.java b/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTests.java similarity index 87% rename from function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTest.java rename to function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTests.java index 2bd6801a..f8111a03 100644 --- a/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTest.java +++ b/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/geo/TwitterGeoFunctionTests.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. @@ -40,13 +40,11 @@ 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.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; -import org.springframework.test.util.TestSocketUtils; import org.springframework.util.MimeTypeUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -58,17 +56,14 @@ /** * @author Christian Tzolov */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = { "twitter.connection.consumerKey=consumerKey666", - "twitter.connection.consumerSecret=consumerSecret666", "twitter.connection.accessToken=accessToken666", - "twitter.connection.accessTokenSecret=accessTokenSecret666" }) +@SpringBootTest(properties = { "twitter.connection.consumerKey=consumerKey666", + "twitter.connection.consumerSecret=consumerSecret666", "twitter.connection.accessToken=accessToken666", + "twitter.connection.accessTokenSecret=accessTokenSecret666" }) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -public abstract class TwitterGeoFunctionTest { +public abstract class TwitterGeoFunctionTests { 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; @@ -90,8 +85,8 @@ public static void recordRequestExpectation(Map> parameters @BeforeAll public static void startMockServer() { - mockServer = ClientAndServer.startClientAndServer(MOCK_SERVER_PORT); - mockClient = new MockServerClient(MOCK_SERVER_IP, MOCK_SERVER_PORT); + mockServer = ClientAndServer.startClientAndServer(); + mockClient = new MockServerClient(MOCK_SERVER_IP, mockServer.getPort()); } @AfterAll @@ -101,7 +96,7 @@ public static void stopMockServer() { @TestPropertySource( properties = { "twitter.geo.search.ip='127.0.0.1'", "twitter.geo.search.query=payload.toUpperCase()" }) - public static class TwitterGeoSearchByIPAndQueryTests extends TwitterGeoFunctionTest { + public static class TwitterGeoSearchByIPAndQueryTests extends TwitterGeoFunctionTests { @Test public void testOne() throws IOException { @@ -125,7 +120,7 @@ public void testOne() throws IOException { assertThat(outPayload).isNotNull(); - List places = new ObjectMapper().readValue(outPayload, List.class); + List places = new ObjectMapper().readValue(outPayload, List.class); assertThat(places).hasSize(12); } @@ -133,7 +128,7 @@ public void testOne() throws IOException { @TestPropertySource(properties = { "twitter.geo.location.lat='52.378'", "twitter.geo.location.lon='4.9'", "twitter.geo.search.query=payload.toUpperCase()" }) - public static class TwitterGeoSearchByLocationTests extends TwitterGeoFunctionTest { + public static class TwitterGeoSearchByLocationTests extends TwitterGeoFunctionTests { @Test public void testOne() throws IOException { @@ -159,7 +154,7 @@ public void testOne() throws IOException { assertThat(outPayload).isNotNull(); - List places = new ObjectMapper().readValue(outPayload, List.class); + List places = new ObjectMapper().readValue(outPayload, List.class); assertThat(places).hasSize(12); } @@ -167,7 +162,7 @@ public void testOne() throws IOException { @TestPropertySource(properties = { "twitter.geo.type=reverse", "twitter.geo.location.lat='52.378'", "twitter.geo.location.lon='4.9'" }) - public static class TwitterGeoSearchByLocation2Tests extends TwitterGeoFunctionTest { + public static class TwitterGeoSearchByLocation2Tests extends TwitterGeoFunctionTests { @Test public void testOne() throws IOException { @@ -191,7 +186,7 @@ public void testOne() throws IOException { assertThat(outPayload).isNotNull(); - List places = new ObjectMapper().readValue(outPayload, List.class); + List places = new ObjectMapper().readValue(outPayload, List.class); assertThat(places).hasSize(12); } @@ -200,7 +195,7 @@ public void testOne() throws IOException { @TestPropertySource(properties = { "twitter.geo.location.lat=#jsonPath(new String(payload),'$.location.lat')", "twitter.geo.location.lon=#jsonPath(new String(payload),'$.location.lon')", "twitter.geo.search.query=#jsonPath(new String(payload),'$.country')" }) - public static class TwitterGeoSearchJsonPathTests extends TwitterGeoFunctionTest { + public static class TwitterGeoSearchJsonPathTests extends TwitterGeoFunctionTests { @Test public void testOne() throws IOException { @@ -228,7 +223,7 @@ public void testOne() throws IOException { assertThat(outPayload).isNotNull(); - List places = new ObjectMapper().readValue(outPayload, List.class); + List places = new ObjectMapper().readValue(outPayload, List.class); assertThat(places).hasSize(12); } @@ -236,7 +231,6 @@ public void testOne() throws IOException { @SpringBootConfiguration @EnableAutoConfiguration - @Import(TwitterGeoFunctionConfiguration.class) public static class TwitterGeoFunctionTestApplication { @Bean @@ -246,7 +240,7 @@ public twitter4j.conf.Configuration twitterConfiguration2(TwitterConnectionPrope Function mockedConfiguration = toConfigurationBuilder .andThen(new TwitterTestUtils() - .mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT))); + .mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, mockServer.getPort()))); return mockedConfiguration.apply(properties).build(); } diff --git a/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionTests.java b/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionTests.java index 08f2e228..1fa07062 100644 --- a/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionTests.java +++ b/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/trend/TwitterTrendFunctionTests.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. @@ -35,13 +35,11 @@ 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.integration.support.MessageBuilder; 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; @@ -61,8 +59,6 @@ public abstract class TwitterTrendFunctionTests { 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; @@ -74,8 +70,8 @@ public abstract class TwitterTrendFunctionTests { @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(MOCK_SERVER_IP, mockServer.getPort()); trendsRequest = setExpectation( request().withMethod("GET").withPath("/trends/place.json").withQueryStringParameter("id", "2972")); @@ -96,7 +92,8 @@ public static HttpRequest setExpectation(HttpRequest request) { return request; } - @TestPropertySource(properties = { "twitter.trend.locationId='2972'", "twitter.connection.rawJson=true" }) + @TestPropertySource(properties = { "twitter.trend.trendQueryType=trend", "twitter.trend.locationId='2972'", + "twitter.connection.rawJson=true" }) public static class TwitterTrendPayloadTests extends TwitterTrendFunctionTests { @Test @@ -110,7 +107,6 @@ public void testOne() { @SpringBootConfiguration @EnableAutoConfiguration - @Import(TwitterTrendFunctionConfiguration.class) public static class TwitterTrendFunctionTestApplication { @Bean @@ -120,7 +116,7 @@ public twitter4j.conf.Configuration twitterConfiguration2(TwitterConnectionPrope Function mockedConfiguration = toConfigurationBuilder .andThen(new TwitterTestUtils() - .mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT))); + .mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, mockServer.getPort()))); return mockedConfiguration.apply(properties).build(); } diff --git a/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionTests.java b/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionTests.java index fa334fbb..4a20981e 100644 --- a/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionTests.java +++ b/function/spring-twitter-function/src/test/java/org/springframework/cloud/fn/twitter/users/TwitterUsersFunctionTests.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. @@ -38,13 +38,11 @@ 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.integration.support.MessageBuilder; 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; @@ -64,8 +62,6 @@ public abstract class TwitterUsersFunctionTests { 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; @@ -94,8 +90,8 @@ public static HttpRequest setExpectation(HttpRequest request, String responseUri @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(MOCK_SERVER_IP, mockServer.getPort()); searchUsersRequest = setExpectation(request().withMethod("GET") .withPath("/users/search.json") @@ -127,7 +123,7 @@ public void testOne() throws IOException { Message received = twitterUsersFunction.apply(MessageBuilder.withPayload("tzolov").build()); mockClient.verify(searchUsersRequest, once()); assertThat(received).isNotNull(); - List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class); + List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class); assertThat(list).hasSize(20); } @@ -144,7 +140,7 @@ public void testOne() throws IOException { mockClient.verify(lookupUsersRequest, once()); assertThat(received).isNotNull(); - List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class); + List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class); assertThat(list).hasSize(5); } @@ -164,7 +160,7 @@ public void testOne() throws IOException { mockClient.verify(lookupUsersRequest2, once()); assertThat(received).isNotNull(); - List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class); + List list = mapper.readValue(new String((byte[]) received.getPayload()), List.class); assertThat(list).hasSize(5); } @@ -172,17 +168,16 @@ public void testOne() throws IOException { @SpringBootConfiguration @EnableAutoConfiguration - @Import(TwitterUsersFunctionConfiguration.class) static class TwitterUsersFunctionTestApplication { @Bean @Primary - public twitter4j.conf.Configuration twitterConfiguration2(TwitterConnectionProperties properties, + twitter4j.conf.Configuration twitterConfiguration2(TwitterConnectionProperties properties, Function toConfigurationBuilder) { Function mockedConfiguration = toConfigurationBuilder .andThen(new TwitterTestUtils() - .mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, MOCK_SERVER_PORT))); + .mockTwitterUrls(String.format("http://%s:%s", MOCK_SERVER_IP, mockServer.getPort()))); return mockedConfiguration.apply(properties).build(); }