Sending email in WSO2 adds spurious forward slash resulting in error 401003 - mailto

I have been chasing this error for months but cannot determine where it is coming from. I have a fault sequence (not written by me) that sends an email address. The relevant part is (I think):
<property name="Subject" scope="transport" type="STRING" value="WSO2 Error Report"/>
<property name="MessageType" scope="axis2" type="STRING" value="text/html"/>
<property name="ContentType" scope="axis2" type="STRING" value="text/html"/>
<header expression="concat('mailto:','email1#domain.org,email2#domain.org')" name="To" scope="default"/>
<property name="FORCE_CONTENT_TYPE_BASED_FORMATTER" scope="axis2" type="STRING" value="true"/>
<property name="FORCE_SC_ACCEPTED" scope="axis2" type="STRING" value="true"/>
<property name="OUT_ONLY" scope="default" type="STRING" value="true"/>
<call>
<endpoint>
<default/>
</endpoint>
</call>
This is the only place where I have two email addresses to send to. When this sequence is triggered I see this error in my Kibana logs:
Message :: = An unexpected error occurred, :: ERROR_MESSAGE :: = Invalid target address/es : email1#domain.org,email2#domain.org/, :: ERROR_CODE :: = 401003
I know what the error is...it's that trailing forward slash, but I have NO idea of where it is coming from. I don't know enough about WSO2 sequences to know where to look. The forward slash isn't in the XML file but where would I look to see how the email is formatted before it is sent? Seems like that would be in the "default" endpoint but I don't know what that is, even after reading up on the WSO2 ESB docs.
Running 6.6.0 of EI.
WSO2 Logs entry/stacktrace:
ERROR {org.apache.axis2.transport.mail.MailTransportSender} - Invalid target address/es : email1#domain.org,email2#domain.org/ javax.mail.internet.AddressException: Domain contains illegal character in string ``email2#domain.org/''
at javax.mail.internet.InternetAddress.checkAddress(InternetAddress.java:1432)
at javax.mail.internet.InternetAddress.parse(InternetAddress.java:1215)
at javax.mail.internet.InternetAddress.parse(InternetAddress.java:752)
at javax.mail.internet.InternetAddress.parse(InternetAddress.java:729)
at org.apache.axis2.transport.mail.MailTransportSender.sendMessage(MailTransportSender.java:172)
at org.apache.axis2.transport.base.AbstractTransportSender.invoke(AbstractTransportSender.java:112)
at org.apache.axis2.engine.AxisEngine.send(AxisEngine.java:442)
at org.apache.axis2.description.OutOnlyAxisOperationClient.executeImpl(OutOnlyAxisOperation.java:297)
at org.apache.axis2.client.OperationClient.execute(OperationClient.java:149)
at org.apache.synapse.core.axis2.Axis2FlexibleMEPClient.send(Axis2FlexibleMEPClient.java:634)
at org.apache.synapse.core.axis2.Axis2Sender.sendOn(Axis2Sender.java:85)
at org.apache.synapse.core.axis2.Axis2SynapseEnvironment.send(Axis2SynapseEnvironment.java:571)
at org.apache.synapse.endpoints.AbstractEndpoint.send(AbstractEndpoint.java:408)
at org.apache.synapse.endpoints.DefaultEndpoint.send(DefaultEndpoint.java:88)
at org.apache.synapse.mediators.builtin.CallMediator.handleNonBlockingCall(CallMediator.java:278)
at org.apache.synapse.mediators.builtin.CallMediator.mediate(CallMediator.java:122)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:109)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:71)
at org.apache.synapse.mediators.base.SequenceMediator.mediate(SequenceMediator.java:158)
at org.apache.synapse.mediators.eip.Target.mediateMessage(Target.java:255)
at org.apache.synapse.mediators.eip.Target.mediate(Target.java:110)
at org.apache.synapse.mediators.eip.splitter.CloneMediator.mediate(CloneMediator.java:119)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:109)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:71)
at org.apache.synapse.mediators.base.SequenceMediator.mediate(SequenceMediator.java:158)
at org.apache.synapse.mediators.MediatorFaultHandler.onFault(MediatorFaultHandler.java:96)
at org.apache.synapse.FaultHandler.handleFault(FaultHandler.java:101)
at org.apache.synapse.mediators.eip.Target.mediateMessage(Target.java:259)
at org.apache.synapse.mediators.eip.Target.mediate(Target.java:132)
at org.apache.synapse.mediators.eip.splitter.CloneMediator.mediate(CloneMediator.java:119)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:109)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:71)
at org.apache.synapse.config.xml.AnonymousListMediator.mediate(AnonymousListMediator.java:37)
at org.apache.synapse.mediators.filters.FilterMediator.mediate(FilterMediator.java:205)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:109)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:71)
at org.apache.synapse.config.xml.AnonymousListMediator.mediate(AnonymousListMediator.java:37)
at org.apache.synapse.mediators.filters.FilterMediator.mediate(FilterMediator.java:205)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:109)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:71)
at org.apache.synapse.mediators.filters.FilterMediator.mediate(FilterMediator.java:171)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:109)
at org.apache.synapse.mediators.AbstractListMediator.mediate(AbstractListMediator.java:71)
at org.apache.synapse.mediators.base.SequenceMediator.mediate(SequenceMediator.java:158)
at org.apache.synapse.rest.Resource.process(Resource.java:344)
at org.apache.synapse.rest.API.process(API.java:441)
at org.apache.synapse.rest.RESTRequestHandler.apiProcess(RESTRequestHandler.java:135)
at org.apache.synapse.rest.RESTRequestHandler.dispatchToAPI(RESTRequestHandler.java:113)
at org.apache.synapse.rest.RESTRequestHandler.process(RESTRequestHandler.java:71)
at org.apache.synapse.core.axis2.Axis2SynapseEnvironment.injectMessage(Axis2SynapseEnvironment.java:327)
at org.apache.synapse.core.axis2.SynapseMessageReceiver.receive(SynapseMessageReceiver.java:98)
at org.apache.axis2.engine.AxisEngine.receive(AxisEngine.java:180)
at org.apache.synapse.transport.passthru.ServerWorker.processNonEntityEnclosingRESTHandler(ServerWorker.java:368)
at org.apache.synapse.transport.passthru.ServerWorker.processEntityEnclosingRequest(ServerWorker.java:427)
at org.apache.synapse.transport.passthru.ServerWorker.run(ServerWorker.java:182)
at org.apache.axis2.transport.base.threads.NativeWorkerPool$1.run(NativeWorkerPool.java:172)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
NORE: The email addresses listed are not the actual addresses but the forwardslash IS as well as the complete stacktrace.

When you want to have dynamic endpoints, one option is to use the default endpoint [1]. Here what happens is that you will create a "TO" header with the URL that you need to invoke. In your case it is the following.
<header expression="concat('mailto:','email1#domain.org,email2#domain.org')" name="To" scope="default"/>
After that when you invoke the default endpoint it will search for the TO header and send the message to this particular URL. The expression concat is used to concatenate the strings. You can refer to the document on mailto transport to further clarify this [2].
As per the given expression there does not seem to have a trailing black slash. But you can try to log this header value and check if there are any unwanted characters added to the URL.
[1] https://docs.wso2.com/display/EI660/Default+Endpoint
[2] https://docs.wso2.com/display/EI660/MailTo+Transport

Related

How to change a property value inside a Apache Camel Route?

In a property file a variable test has been defined:
test=OLD_VALUE
In the following Spring-DSL definition a camel route is defined. Properties are loaded via PropertiesComponent.
<bean id="properties" class="org.apache.camel.component.properties.PropertiesComponent">
<property name="cache" value="false"/>
<property name="location" value="classpath:res.properties"/>
</bean>
<camelContext id="ctx" xmlns="http://camel.apache.org/schema/spring">
<route id="toParamRoute">
<from uri="servlet:myParam"/>
HERE I WOULD LIKE TO SET THE
VARIABLE TEST WITH A NEW VALUE,
SUCH THAT THE FOLLOWING LOG MESSAGE
WILL PRINT THE NEW VALUE,
E.G: test=NEW_VALUE
<log message="{{test}}"/>
</route>
</camelContext>
I tried different approach using groovy, language script expression, external spring bean but without success. Is there a way to set and change the value of a variable loaded at startup?
What is the best way to do it?
Anyone can help me? I did not find any similar question on stackoverflow! The problem I am facing and the solution I am looking for is a basic building-block to build a WEB UI management console to change some behavior of routes on the fly. To simplify the flow I can say that after propertyPlaceholder has loaded a property file then via a UI web page the default parameters of routes can be changed, and only after the route can be started.
Properties evaluated with syntax {{property}} are resolved only once during context initialization. If you need to reflect runtime changes, use Simple language
Example:
<bean id="myProperties" class="java.util.Properties"/>
<bean id="properties" class="org.apache.camel.component.properties.PropertiesComponent">
<property name="cache" value="false"/>
<property name="location" value="classpath:res.properties"/>
<property name="overrideProperties" ref="myProperties" />
</bean>
<camelContext id="ctx" xmlns="http://camel.apache.org/schema/spring">
<route id="toParamRoute">
<from uri="timer://foo"/>
<log message="About to change property test from value ${properties:test} to value ${exchangeProperty.CamelTimerCounter}. Initial value was {{test}}"/>
<bean ref="myProperties" method="setProperty(test, ${exchangeProperty.CamelTimerCounter})" />
<log message="New value is ${properties:test}"/>
</route>
</camelContext>

Spring JMS using MarshallingMessageConverter while supporting String, etc

Using XML configuration - I have MarshallingMessageConverter working; however, I still want to send some messages as TextMessage with simple String values.
It seems that my configuration is forcing me to go from one ditch (No automatic JAXB marshalling) into the other (JAXB marshalling only):
Here's my relevant XML configuration:
<bean id="jaxbConverter" class="org.springframework.jms.support.converter.MarshallingMessageConverter">
<property name="marshaller" ref="jaxb2Marshaller" />
<property name="unmarshaller" ref="jaxb2Marshaller" />
</bean>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="errorHandler" ref="jmsErrorHandler" />
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="jmsDestinationResolver"/>
<property name="messageConverter" ref="jaxbConverter" />
</bean>
The JAXB marshalling works fine, but I sometimes want to send an empty body (header properties only) message; which causes an error like so:
.UnmarshallingFailureException: JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.]
Which makes sense, because I'm sending a non-JAXB string (or nothing) in the body.
Is it possible to have the best of both worlds; the String, byte[], Map conversion behavior of org.springframework.jms.support.converter.SimpleMessageConverter -and- org.springframework.jms.support.converter.MarshallingMessageConverter ?
Is the only way to accomplish this by making a second container factory with the other converter and explicitly using it in my #JmsListener annotation?
Create a simple DelegatingMessageConverter(implement MessageConverter) and have it delegate to the SimpleMessageConverter, MarshallingMessageConverter (or even a MappingJackson2MessageConverter) based on a message property, e.g. message.getStringProperty("contentType") - text/plain, application/xml, application/json.

SharePoint Search + BCS and meaningless non-human-readable urls

Indexing an external content type is pretty straight forward.
However I find it very troublesome to figure out the specific "crawl state" of a particular item.
Looking at the "Url View" inside SharePoint CA it shows me all my crawled/indexed items. Unfortunately the url part is nowhere near readable. So I have no clue where to look for my specific item in question.
Example:
bdc3://adventureworksdbtest/Default/00000000%2D0000%2D0000%2D0000%2D000000000000/418/AdventureworksDB/420?s_id=i0QMAAA==&s_ce=0408808680g000g10204000g0o20003s
Guid.Empty?
418?
420?
Some encoded id? (it's not BASE64. i0QMAAA== translates to nothing).
The AdventureWorks product table has a productID (int) and a name (string). In my BDC model I mapped the "Title" column to the product's "Name" column for my Product entity. See below...
<Entity Name="Product" Namespace="Rs.Exp.IndexingConnector.BuiltinDbConnector" Version="1.0.0.1">
<Properties>
<Property Name="OriginalName" Type="System.String">[Production].[Product]</Property>
<Property Name="EntitySetName" Type="System.String">[Production].[Product]</Property>
<Property Name="Title" Type="System.String">Name</Property>
</Properties>
<Identifiers>
<Identifier Name="ProductID" TypeName="System.Int32" />
</Identifiers>
<!-- [...] -->
</Entity>
That's working. However it has no influence on the "index url". Any hints? Something I have to live with? How do you debug this?
Update: DisplayUriField sounds like a good thing to look into. Although all I found so far is not what I am looking for. I'm not looking for a way to provide an url myself (where would I link to anyway?!) - I just want the "generated url" to be more meaningful.
Answering my own question in case anyone else is wondering how this works.
The Method(Instance) Property "DisplayUriField" is in fact providing the desired functionality. As a value you can set the name of a TypeDescriptor which is part of your entity schema.
For example if you have a TypeDescriptor like the one below (make sure your SELECT statement returns the column in question)...
<TypeDescriptor Name="Url" TypeName="System.String">
<Properties>
<Property Name="ShowInPicker" Type="System.Boolean">false</Property>
</Properties>
</TypeDescriptor>
...you can use it as a value for your DisplayUriField property:
<MethodInstances>
<MethodInstance Name="GetAllProducts" Type="Finder" ReturnParameterName="GetAllProducts_Returned" Default="true" DefaultDisplayName="Get All Products">
<Properties>
<Property Name="RootFinder" Type="System.String">x</Property>
<Property Name="LastModifiedTimeStampField" Type="System.String">ModifiedDate</Property>
<Property Name="DisplayUriField" Type="System.String">Url</Property>
</Properties>
</MethodInstance>
</MethodInstances>
My Database Table Url column contains urls like this
http://localhost/adventurworks/catalog/1
http://localhost/adventurworks/catalog/316
http://localhost/adventurworks/catalog/317
[...]
Whatever is in your column will be shown in SharePoints Crawl Log ("Url View").
If your column ("Url") is nullable and does not contain any value BDC will use a default name for that specific item.
Of course after all the custom url is pretty damn useless. However it allows me to figure out whether one specific item has been crawled (successfully) or not because I can relate the shown Url to the ProductID - which I cannot with those bdc3:// urls.

Spring integration: how to handle exceptions in services after an aggregator?

I have an application relying on Spring Integration (4.0.4.RELEASE) and RabbitMQ. My flow is as follow:
Messages are put in queue via a process (they do not expect any answer):
Gateway -> Channel -> RabbitMQ
And then drained by another process:
RabbitMQ --1--> inbound-channel-adapter A --2--> chain B --3--> aggregator C --4--> service-activator D --5--> final service-activator E
Explanations & context
The specific thing is that nowhere in my application I am using a splitter: aggregator C just waits for enough messages to come, or for a timeout to expire, and then forwards the batch to service D. Messages can get stuck in aggregator C for quite a long time, and should NOT be considered as consumed there. They should only be consumed once service D successfully completes. Therefore, I am using MANUAL acknowledgement on inbound-channel-adapter A and service E is in charge of acknowledging the batch.
Custom aggregator
I solved the acknowledgement issue I had when set to AUTO by redefining the aggregator. Indeed, messages are acknowledged immediately if any asynchronous process occurs in the flow (see question here). Therefore, I switched to MANUAL acknowledgement and implemented the aggregator like this:
<bean class="org.springframework.integration.config.ConsumerEndpointFactoryBean">
<property name="inputChannel" ref="channel3"/>
<property name="handler">
<bean class="org.springframework.integration.aggregator.AggregatingMessageHandler">
<constructor-arg name="processor">
<bean class="com.test.AMQPAggregator"/>
</constructor-arg>
<property name="correlationStrategy">
<bean class="com.test.AggregatorDefaultCorrelationStrategy" />
</property>
<property name="releaseStrategy">
<bean class="com.test.AggregatorMongoReleaseStrategy" />
</property>
<property name="messageStore" ref="messageStoreBean"/>
<property name="expireGroupsUponCompletion" value="true"/>
<property name="sendPartialResultOnExpiry" value="true"/>
<property name="outputChannel" ref="channel4"/>
</bean>
</property>
</bean>
<bean id="messageStoreBean" class="org.springframework.integration.store.SimpleMessageStore"/>
<bean id="messageStoreReaperBean" class="org.springframework.integration.store.MessageGroupStoreReaper">
<property name="messageGroupStore" ref="messageStore" />
<property name="timeout" value="${myapp.timeout}" />
</bean>
<task:scheduled-tasks>
<task:scheduled ref="messageStoreReaperBean" method="run" fixed-rate="2000" />
</task:scheduled-tasks>
I wanted indeed to aggregate the headers in a different way, and keep the highest value of all the amqp_deliveryTag for later multi-acknoledgement in service E (see this thread). This works great so far, apart from the fact that it is far more verbose than the typical aggregator namespace (see this old Jira ticket).
Services
I am just using basic configurations:
chain-B
<int:chain input-channel="channel2" output-channel="channel3">
<int:header-enricher>
<int:error-channel ref="errorChannel" /> // Probably useless
</int:header-enricher>
<int:json-to-object-transformer/>
<int:transformer ref="serviceABean"
method="doThis" />
<int:transformer ref="serviceBBean"
method="doThat" />
</int:chain>
service-D
<int:service-activator ref="serviceDBean"
method="doSomething"
input-channel="channel4"
output-channel="channel5" />
Error management
As I rely on MANUAL acknowledgement, I need to manually reject messages as well in case an exception occurs. I have the following definition for inbound-channel-adapter A:
<int-amqp:inbound-channel-adapter channel="channel2"
queue-names="si.queue1"
error-channel="errorChannel"
mapped-request-headers="*"
acknowledge-mode="MANUAL"
prefetch-count="${properties.prefetch_count}"
connection-factory="rabbitConnectionFactory"/>
I use the following definition for errorChannel:
<int:chain input-channel="errorChannel">
<int:transformer ref="errorUnwrapperBean" method="unwrap" />
<int:service-activator ref="amqpAcknowledgerBean" method="rejectMessage" />
</int:chain>
ErrorUnwrapper is based on this code and the whole exception detection and message rejection works well until messages reach aggregator C.
Problem
If an exception is raised while processing the messages in service-activator D, then I see this exception but errorChannel does not seem to receive any message, and my ErrorUnwrapper unwrap() method is not called. The tailored stack traces I see when an Exception("ahahah") is thrown are as follow:
2014-09-23 16:41:18,725 ERROR o.s.i.s.SimpleMessageStore:174: Exception in expiry callback
org.springframework.messaging.MessageHandlingException: java.lang.Exception: ahahaha
at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:78)
at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:71)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:170)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
(...)
Caused by: java.lang.Exception: ahahaha
at com.myapp.ServiceD.doSomething(ServiceD.java:153)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
(...)
2014-09-23 16:41:18,733 ERROR o.s.s.s.TaskUtils$LoggingErrorHandler:95: Unexpected error occurred in scheduled task.
org.springframework.messaging.MessageHandlingException: java.lang.Exception: ahahaha
(...)
Question
How can one tell the services that process messages coming from such an aggregator to publish errors to errorChannel? I tried to specify in the header via a header-enricher the error-channel with no luck. I am using the default errorChannel definition, but I tried as well to change its name and redefine it. I am clueless here, and even though I found this and that, I have not managed to get it to work. Thanks in advance for your help!
As you see by StackTrace your process is started from the MessageGroupStoreReaper Thread, which is initiated from the default ThreadPoolTaskScheduler.
So, you must provide a custom bean for that:
<bean id="scheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="errorHandler">
<bean class="org.springframework.integration.channel.MessagePublishingErrorHandler">
<property name="defaultErrorChannel" ref="errorChannel"/>
</bean>
</property>
</bean>
<task:scheduled-tasks scheduler="scheduler">
<task:scheduled ref="messageStoreReaperBean" method="run" fixed-rate="2000" />
</task:scheduled-tasks>
However I see the benefits from having the error-channel on the <aggregator>, where we really have several points from different detached Threads, with wich we can't get deal normally.

Spring Integration Multiplexing correlation

I am new to SI. I am using the code from the SI TCP Multiplexing example as a starting point for an app server I am writing. The caller of the service already exists and will be sending the payload prefixed by a byte length header. I am having a bit of trouble with the correlation of the response. As you can see below, I changed the Multiplexing example to first add a correlation id header to the incoming request before pushing on to the publish-subscribe-channel. The rest of the code is pretty much the same as the example.
So, the problem. The correlation id header is not available on the call to MessageController from the TcpSendingMessageHandler which serializes the message payload and sends it. Should I enrich the payload to include the correlation id (no correlation header) or is there a simpler way of doing all of this? Any guidance would be greatly appreciated.
<gateway id="gw"
service-interface="is.point.tokens.server.MessageGateway"
default-request-channel="input">
</gateway>
<ip:tcp-connection-factory id="client"
type="client"
host="${tcpClientServer.address}"
port="${tcpClientServer.port}"
single-use="false"
serializer="bigEndianFormatSerializer"
deserializer="bigEndianFormatSerializer"
so-timeout="10000"/>
<channel id="input" datatype="java.lang.String"/>
<header-enricher input-channel="input" output-channel="enriched.input">
<correlation-id expression="headers['id']"/>
</header-enricher>
<publish-subscribe-channel id="enriched.input"/>
<ip:tcp-outbound-channel-adapter id="outAdapter.client"
order="2"
channel="enriched.input"
connection-factory="client"/>
<!-- Collaborator -->
<!-- Also send a copy to the custom aggregator for correlation and
so this message's replyChannel will be transferred to the
aggregated message. The order ensures this gets to the aggregator first -->
<bridge input-channel="enriched.input" output-channel="toAggregator.client" order="1"/>
<!-- Asynchronously receive reply. -->
<ip:tcp-inbound-channel-adapter id="inAdapter.client"
channel="toAggregator.client"
connection-factory="client"/>
<!-- Collaborator -->
<channel id="toAggregator.client" datatype="java.lang.String"/>
<aggregator input-channel="toAggregator.client"
output-channel="toTransformer.client"
correlation-strategy-expression="headers.get('correlationId')"
release-strategy-expression="size() == 2">
</aggregator>
<!-- The response is always second -->
<transformer input-channel="toTransformer.client" expression="payload.get(1)"/>
<!-- Server side -->
<ip:tcp-connection-factory id="server"
type="server"
port="${tcpClientServer.port}"
using-nio="true"
serializer="bigEndianFormatSerializer"
deserializer="bigEndianFormatSerializer"/>
<ip:tcp-inbound-channel-adapter id="inAdapter.server"
channel="toSA"
connection-factory="server" />
<channel id="toSA" datatype="java.lang.String"/>
<service-activator input-channel="toSA"
output-channel="toObAdapter"
ref="messageController"
method="handleMessage"/>
<beans:bean id="messageController"
class="example.server.MessageController"/>
<channel id="toObAdapter"/>
<ip:tcp-outbound-channel-adapter id="outAdapter.server"
channel="toObAdapter"
connection-factory="server"/>
Yes, you need something in the data so the reply can be correlated.
But I am confused. If you are the server side for an existing application, then you can simply use an inbound gateway.
If you really are providing the client and server side, we did add a mechanism in 3.0 to selectively add headers (to the tcp message), for example, using JSON.
EDIT:
From your comments, you only need the server side. All you need is this...
<int-ip:tcp-connection-factory id="server"
type="server"
serializer="serializer"
deserializer="serializer"
port="${port}"/>
<int-ip:tcp-inbound-gateway id="gateway"
connection-factory="crLfServer"
request-channel="toSA"
error-channel="errorChannel"/>
<int:channel id="toSA" />
<int:service-activator input-channel="toSA"
ref="messageController"
method="handleMessage"/>
<bean id="serializer" class="org.springframework.integration.ip.tcp.serializer.ByteArrayLengthHeaderSerializer" />
The fact that the service-activator has no output-channel means the framework will send the reply to the gateway automatically.
This assumes the service can handle a byte[]; if you need a String, you'll need a transformer like in the sample. Either way, the payload will not include the length header; it is stripped off (inbound) and added (outbound).
This also assumes the length header is 4 bytes (default); the serializer takes a size in a constructor...
<bean id="serializer" class="org.springframework.integration.ip.tcp.serializer.ByteArrayLengthHeaderSerializer">
<constructor-arg value="2" />
</bean>

Resources