diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterOutboundChannelAdapterParser.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterOutboundChannelAdapterParser.java index 6a07e212fe..2070be1467 100644 --- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterOutboundChannelAdapterParser.java +++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/config/TwitterOutboundChannelAdapterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors + * Copyright 2002-2014 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,15 +21,18 @@ import org.w3c.dom.Element; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; import org.springframework.integration.twitter.outbound.DirectMessageSendingMessageHandler; import org.springframework.integration.twitter.outbound.StatusUpdatingMessageHandler; +import org.springframework.util.StringUtils; /** * Parser for all outbound Twitter adapters - * + * * @author Josh Long * @author Oleg Zhurakousky + * @author Artem Bilan * @since 2.0 */ public class TwitterOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { @@ -39,6 +42,13 @@ public class TwitterOutboundChannelAdapterParser extends AbstractOutboundChannel Class clazz = determineClass(element, parserContext); BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); builder.addConstructorArgReference(element.getAttribute("twitter-template")); + String tweetDataExpression = element.getAttribute("tweet-data-expression"); + if (StringUtils.hasText(tweetDataExpression)) { + builder.addPropertyValue("tweetDataExpression", + BeanDefinitionBuilder.genericBeanDefinition(ExpressionFactoryBean.class) + .addConstructorArgValue(tweetDataExpression) + .getBeanDefinition()); + } return builder.getBeanDefinition(); } diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandler.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandler.java index ed5715e7f1..4be21e92e3 100644 --- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandler.java +++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandler.java @@ -16,10 +16,16 @@ package org.springframework.integration.twitter.outbound; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.TypeLocator; +import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.integration.expression.IntegrationEvaluationContextAware; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandlingException; import org.springframework.social.twitter.api.Tweet; +import org.springframework.social.twitter.api.TweetData; import org.springframework.social.twitter.api.Twitter; import org.springframework.util.Assert; @@ -28,37 +34,80 @@ import org.springframework.util.Assert; * * @author Josh Long * @author Oleg Zhurakousky + * @author Artem Bilan * @since 2.0 */ -public class StatusUpdatingMessageHandler extends AbstractMessageHandler { +public class StatusUpdatingMessageHandler extends AbstractMessageHandler + implements IntegrationEvaluationContextAware { private final Twitter twitter; + private volatile Expression tweetDataExpression; + + private EvaluationContext evaluationContext; public StatusUpdatingMessageHandler(Twitter twitter) { Assert.notNull(twitter, "twitter must not be null"); this.twitter = twitter; } + @Override + public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { + TypeLocator typeLocator = evaluationContext.getTypeLocator(); + if (typeLocator instanceof StandardTypeLocator) { + /* + * Register the twitter api package so they don't need a FQCN for TweetData. + */ + ((StandardTypeLocator) typeLocator).registerImport("org.springframework.social.twitter.api"); + } + this.evaluationContext = evaluationContext; + } + @Override public String getComponentType() { return "twitter:outbound-channel-adapter"; } + /** + * An expression that is used to build the {@link TweetData}; must resolve to a + * {@link TweetData} object, or a {@link String}, or a {@link Tweet}. + *

When using a {@code TweetData} directly in the expression, it is not necessary + * to include the package: + * {@code "new TweetData("test").withMedia(headers.mediaResource).displayCoordinates(true)")}. + * @param tweetDataExpression The expression. + * @since 4.0 + */ + public void setTweetDataExpression(Expression tweetDataExpression) { + this.tweetDataExpression = tweetDataExpression; + } + @Override protected void handleMessageInternal(Message message) throws Exception { - Object payload = message.getPayload(); - String statusText = null; - if (payload instanceof Tweet) { - statusText = ((Tweet) payload).getText(); - } - else if (payload instanceof String) { - statusText = (String) payload; + Object value; + if (this.tweetDataExpression != null) { + value = this.tweetDataExpression.getValue(this.evaluationContext, message); } else { - throw new MessageHandlingException(message, "Unsupported payload type '" + payload.getClass().getName() + "'"); + value = message.getPayload(); } - this.twitter.timelineOperations().updateStatus(statusText); + Assert.notNull(value, "The tweetData cannot evaluate to 'null'."); + + TweetData tweetData = null; + + if (value instanceof TweetData) { + tweetData = (TweetData) value; + } + else if (value instanceof Tweet) { + tweetData = new TweetData(((Tweet) value).getText()); + } + else if (value instanceof String) { + tweetData = new TweetData((String) value); + } + else { + throw new MessageHandlingException(message, "Unsupported tweetData: " + value); + } + + this.twitter.timelineOperations().updateStatus(tweetData); } } diff --git a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/TwitterSearchOutboundGateway.java b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/TwitterSearchOutboundGateway.java index a2b5dcab87..1e77ec5473 100644 --- a/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/TwitterSearchOutboundGateway.java +++ b/spring-integration-twitter/src/main/java/org/springframework/integration/twitter/outbound/TwitterSearchOutboundGateway.java @@ -22,7 +22,6 @@ import java.util.List; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.TypeLocator; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.integration.expression.IntegrationEvaluationContextAware; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; @@ -50,7 +49,7 @@ public class TwitterSearchOutboundGateway extends AbstractReplyProducingMessageH private final Twitter twitter; - private volatile Expression searchArgsExpression = new SpelExpressionParser().parseExpression("payload"); + private volatile Expression searchArgsExpression; private volatile EvaluationContext evaluationContext; @@ -102,7 +101,13 @@ public class TwitterSearchOutboundGateway extends AbstractReplyProducingMessageH @Override protected Object handleRequestMessage(Message requestMessage) { - Object args = this.searchArgsExpression.getValue(this.evaluationContext, requestMessage); + Object args; + if (this.searchArgsExpression != null) { + args = this.searchArgsExpression.getValue(this.evaluationContext, requestMessage); + } + else { + args = requestMessage.getPayload(); + } Assert.notNull(args, "The twitter search expression cannot evaluate to 'null'."); SearchParameters searchParameters; if (args instanceof SearchParameters) { diff --git a/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-4.0.xsd b/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-4.0.xsd index 5fc97d0120..e810d9989b 100644 --- a/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-4.0.xsd +++ b/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-4.0.xsd @@ -113,6 +113,16 @@ + + + + A SpEL expression that evaluates to tweetData; the evaluation result type can be + an 'org.springframework.social.twitter.api.TweetData', a 'String' or + 'org.springframework.social.twitter.api.Tweet'. + Default: "payload". + + + diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandlerTests-context.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandlerTests-context.xml new file mode 100644 index 0000000000..59e46de663 --- /dev/null +++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandlerTests-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandlerTests.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandlerTests.java index 4b6cf488bc..eec218b4b6 100644 --- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandlerTests.java +++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/outbound/StatusUpdatingMessageHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors + * Copyright 2002-2014 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,37 +16,93 @@ package org.springframework.integration.twitter.outbound; +import static org.junit.Assert.assertEquals; + +import java.util.Collections; import java.util.Properties; import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.core.io.ClassPathResource; -import org.springframework.messaging.Message; import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.support.GenericMessage; +import org.springframework.social.twitter.api.TimelineOperations; +import org.springframework.social.twitter.api.TweetData; +import org.springframework.social.twitter.api.Twitter; import org.springframework.social.twitter.api.impl.TwitterTemplate; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.MultiValueMap; /** * @author Oleg Zhurakousky + * @author Artem Bilan * @since 2.0 */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext public class StatusUpdatingMessageHandlerTests { - + @Autowired + MessageChannel in1; + + @Autowired + MessageChannel in2; + + @Autowired + Twitter twitter; + @Test @Ignore public void demoSendStatusMessage() throws Exception{ PropertiesFactoryBean pf = new PropertiesFactoryBean(); pf.setLocation(new ClassPathResource("sample.properties")); pf.afterPropertiesSet(); Properties prop = pf.getObject(); - TwitterTemplate template = new TwitterTemplate(prop.getProperty("z_oleg.oauth.consumerKey"), - prop.getProperty("z_oleg.oauth.consumerSecret"), - prop.getProperty("z_oleg.oauth.accessToken"), + TwitterTemplate template = new TwitterTemplate(prop.getProperty("z_oleg.oauth.consumerKey"), + prop.getProperty("z_oleg.oauth.consumerSecret"), + prop.getProperty("z_oleg.oauth.accessToken"), prop.getProperty("z_oleg.oauth.accessTokenSecret")); - Message message1 = MessageBuilder.withPayload("Ppolishing #springintegration migration to Spring Social. test").build(); + Message message1 = MessageBuilder.withPayload("Polishing #springintegration migration to Spring Social. test").build(); StatusUpdatingMessageHandler handler = new StatusUpdatingMessageHandler(template); handler.afterPropertiesSet(); handler.handleMessage(message1); } + @Test + public void testStatusUpdatingMessageHandler() { + TimelineOperations timelineOperations = Mockito.mock(TimelineOperations.class); + Mockito.when(this.twitter.timelineOperations()).thenReturn(timelineOperations); + + ArgumentCaptor argument = ArgumentCaptor.forClass(TweetData.class); + + this.in1.send(new GenericMessage("foo")); + + Mockito.verify(timelineOperations).updateStatus(argument.capture()); + assertEquals("foo", argument.getValue().toRequestParameters().getFirst("status")); + + Mockito.reset(timelineOperations); + + ClassPathResource media = new ClassPathResource("log4j.properties"); + this.in2.send(MessageBuilder.withPayload(Collections.singletonMap("foo", "bar")) + .setHeader("media", media) + .build()); + + Mockito.verify(timelineOperations).updateStatus(argument.capture()); + MultiValueMap requestParameters = argument.getValue().toRequestParameters(); + assertEquals("bar", requestParameters.getFirst("status")); + assertEquals(media, requestParameters.getFirst("media")); + + + } + } diff --git a/src/reference/docbook/twitter.xml b/src/reference/docbook/twitter.xml index 7e2fe2c3ee..2e0213337a 100644 --- a/src/reference/docbook/twitter.xml +++ b/src/reference/docbook/twitter.xml @@ -253,7 +253,24 @@ twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtxFK8w5ZMtMj20KFhB6o]]>]]> The only extra configuration that is required for this adapter is the twitter-template reference. - + + Starting with version 4.0 the <int-twitter:outbound-channel-adapter> + supports a tweet-data-expression to populate the TweetData argument + (Spring Social Twitter) using the + message as the root object of the expression evaluation context. The result can be a String, + which will be used for the TweetData message; a Tweet object, the + text of which will be used for the TweetData message; or an entire + TweetData object. For convenience, the TweetData can be built + from the expression directly without needing a fully qualified class name: + ]]> + + + This allows, for example, attaching an image to the tweet. + +

Twitter Outbound Direct Message Channel Adapter diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index f723c8cb00..17fbeaaec4 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -322,5 +322,15 @@ advanced configuration. See for more information.
+
+ Twitter: StatusUpdatingMessageHandler + + The StatusUpdatingMessageHandler (<int-twitter:outbound-channel-adapter>) + now supports the tweet-data-expression attribute to build a + org.springframework.social.twitter.api.TweetData object for updating the + timeline status allowing, for example, attaching an image. + See for more information. + +