INT-3065: Add tweet-data-expression for Update
JIRA: https://jira.spring.io/browse/INT-3065 Improve `TwitterSearchOutboundGateway` to use an expression to build the tweet instead of just using the payload (which remains the default).
This commit is contained in:
committed by
Gary Russell
parent
9052377bf5
commit
97d1d389a9
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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}.
|
||||
* <p> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -113,6 +113,16 @@
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="outbound-twitter-type">
|
||||
<xsd:attribute name="tweet-data-expression" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
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".
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attributeGroup ref="integration:channelAdapterAttributes"/>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-twitter="http://www.springframework.org/schema/integration/twitter"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/twitter http://www.springframework.org/schema/integration/twitter/spring-integration-twitter.xsd">
|
||||
|
||||
<bean id="tt" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.social.twitter.api.Twitter" />
|
||||
</bean>
|
||||
|
||||
<int-twitter:outbound-channel-adapter id="in1" twitter-template="tt" />
|
||||
|
||||
<int-twitter:outbound-channel-adapter
|
||||
id="in2"
|
||||
twitter-template="tt"
|
||||
tweet-data-expression="new TweetData(payload.foo).withMedia(headers.media)"/>
|
||||
|
||||
</beans>
|
||||
@@ -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<TweetData> argument = ArgumentCaptor.forClass(TweetData.class);
|
||||
|
||||
this.in1.send(new GenericMessage<String>("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<String, Object> requestParameters = argument.getValue().toRequestParameters();
|
||||
assertEquals("bar", requestParameters.getFirst("status"));
|
||||
assertEquals(media, requestParameters.getFirst("media"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -253,7 +253,24 @@ twitter.oauth.accessTokenSecret=AbRxUAvyNCtqQtxFK8w5ZMtMj20KFhB6o]]></programlis
|
||||
channel="twitterChannel"/>]]></programlisting>
|
||||
The only extra configuration that is required for this adapter is the <code>twitter-template</code> reference.
|
||||
</para>
|
||||
</section>
|
||||
<para>
|
||||
Starting with <emphasis>version 4.0</emphasis> the <code><int-twitter:outbound-channel-adapter></code>
|
||||
supports a <code>tweet-data-expression</code> to populate the <classname>TweetData</classname> argument
|
||||
(<ulink url="http://projects.spring.io/spring-social-twitter/">Spring Social Twitter</ulink>) using the
|
||||
message as the root object of the expression evaluation context. The result can be a <classname>String</classname>,
|
||||
which will be used for the <classname>TweetData</classname> message; a <classname>Tweet</classname> object, the
|
||||
<code>text</code> of which will be used for the <classname>TweetData</classname> message; or an entire
|
||||
<classname>TweetData</classname> object. For convenience, the <classname>TweetData</classname> can be built
|
||||
from the expression directly without needing a fully qualified class name:
|
||||
<programlisting language="xml"><![CDATA[<int-twitter:outbound-channel-adapter
|
||||
twitter-template="twitterTemplate"
|
||||
channel="twitterChannel"
|
||||
tweet-data-expression="new TweetData(payload).withMedia(headers.media).displayCoordinates(true)/>]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
This allows, for example, attaching an image to the tweet.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="outbound-twitter-direct">
|
||||
<title>Twitter Outbound Direct Message Channel Adapter</title>
|
||||
|
||||
@@ -322,5 +322,15 @@
|
||||
advanced configuration. See <xref linkend="ftp-session-factory"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
<section id="4.0-twitter-status-updating">
|
||||
<title>Twitter: StatusUpdatingMessageHandler</title>
|
||||
<para>
|
||||
The <classname>StatusUpdatingMessageHandler</classname> (<code><int-twitter:outbound-channel-adapter></code>)
|
||||
now supports the <code>tweet-data-expression</code> attribute to build a
|
||||
<classname>org.springframework.social.twitter.api.TweetData</classname> object for updating the
|
||||
timeline status allowing, for example, attaching an image.
|
||||
See <xref linkend="outbound-twitter-update"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user