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.
Related
I'm currently working with citrus-framework to test an application.
One of my interfaces uses Protobuf and I would like to implement a protobuf-to-json-transformer which is compatible with spring-integration to use it similarly like the following but with my transformer instead of the object-to-string-transformer:
<int:channel id="configRawReplies" />
<int:object-to-string-transformer id="configtransformer" input-channel="configRawReplies" output-channel="configResponse" />
<int:channel id="configResponse">
<int:queue />
</int:channel>
for now I have a prototyp exactly like object-to-string-transformer and I'm loading it with:
<bean id="Proto2Json" class="com.nobody.citrus.transformer.ProtoToJSONString">
<property name="input-channel" value="none"/>
<property name="output-channel" value="none"/>
</bean>
but it fails.
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'Proto2Json' defined in URL [file:/Users/nobody/DevOops/test/citrus-scala/target/test-classes/citrus-context.xml]:
Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException:
Invalid property 'input-channel' of bean class [com.pme.citrus.transformer.ProtoToJSONString]:
Bean property 'input-channel' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
Does somebody have an idea or an hint where to look on the web?
BR
That's correct. You really need to follow a design in the ObjectToStringTransformer to implement your own AbstractPayloadTransformer. And that one has to be as a plain <bean> definition in your application context.
Only the problem that you don't understand why we really have all those custom tags to utilize input-channel and output-channel attributes as well. The point is that this
<int:object-to-string-transformer>, for example, provides for the application context several beans, including the mentioned ObjectToStringTransformer instance, a MessageTransformingHandler and, finally, ConsumerEndpointFactoryBean to connect a MessageHandler with an inputChannel.
So, what you are missing here is a generic <int:transformer> definition for your custom AbstractPayloadTransformer implementation:
<bean id="Proto2Json" class="com.nobody.citrus.transformer.ProtoToJSONString"/>
<int:tranformer ref="Proto2Json" input-channel="configRawReplies" output-channel="configResponse"/>
Please, read more Reference Manual to avoid similar discussions in the future:
https://docs.spring.io/spring-integration/reference/html/overview.html#programming-tips
https://docs.spring.io/spring-integration/reference/html/messaging-transformation-chapter.html
So I'm trying to parse an xml and unmarshal it into a program. This is the example xml:
<MaintenanceTransaction:provideCommunicationEvents_BatchRequest
xmlns:MaintenanceTransaction="http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MaintenanceTransaction:maintenanceTransaction>
<MaintenanceTransaction:eventInitiatedBy>
<MaintenanceTransaction:identifier>FINACLE</MaintenanceTransaction:identifier>
</MaintenanceTransaction:eventInitiatedBy>
<MaintenanceTransaction:transactionDate>2014-06-21T19:00:32.356+01:00</MaintenanceTransaction:transactionDate>
<MaintenanceTransaction:maintenanceTransactionType>PRE-NOTIFICATION
</MaintenanceTransaction:maintenanceTransactionType>
<MaintenanceTransaction:maintenanceEntries>
<MaintenanceTransaction:hasNewValues xsi:type="MaintenanceTransaction:DepositArrangement">
<MaintenanceTransaction:enterpriseId xsi:type="MaintenanceTransaction:ArrangementIdentifier">
<MaintenanceTransaction:identifier>222000000322</MaintenanceTransaction:identifier>
<MaintenanceTransaction:enterpriseIdType>Term Deposit</MaintenanceTransaction:enterpriseIdType>
</MaintenanceTransaction:enterpriseId>
<MaintenanceTransaction:maturityDate>2014-10-08</MaintenanceTransaction:maturityDate>
</MaintenanceTransaction:hasNewValues>
</MaintenanceTransaction:maintenanceEntries>
</MaintenanceTransaction:maintenanceTransaction>
</MaintenanceTransaction:provideCommunicationEvents_BatchRequest>
The xsd is defined as:
<xsd:schema xmlns:MaintenanceTransaction="http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02" elementFormDefault="qualified" attributeFormDefault="qualified">
<xsd:include schemaLocation="./commonIFWxsd/IFWXML.xsd" />
<xsd:include schemaLocation="./commonIFWxsd/Event.xsd" />
<xsd:include schemaLocation="./commonIFWxsd/Arrangement.xsd" />
<!-- end of TYPES REQUIRED FOR PARAMETERS -->
<xsd:complexType name="provideCommunicationEvents_BatchRequest">
<xsd:sequence>
<xsd:element name="maintenanceTransaction" type="MaintenanceTransaction:MaintenanceTransaction" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<!-- TYPES REQUIRED FOR PARAMETERS -->
<xsd:element name="provideCommunicationEvents_BatchRequest" type="MaintenanceTransaction:provideCommunicationEvents_BatchRequest" />
</xsd:schema>
With the following setup of my StaxEventItemReader and Jaxb2Marshaller:
<bean id="uploadEventMessageReader" parent="abstractUploadEventMessageReader" scope="step">
<property name="resource" value="file:#{jobExecutionContext['fileToProcess']}"/>
<property name="fragmentRootElementName" value="maintenanceTransaction"/>
<property name="unmarshaller" ref="maintenanceTransactionUnmarshaller"/>
</bean>
<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
<property name="classesToBeBound">
<list>
<value>com.pwx.crs.informationcollection.ifw.process.model.mt.MaintenanceTransaction</value>
</list>
</property>
</bean>
The problem however is that I get the following exception.
* [javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02", local:"maintenanceTransaction"). Expected elements are (none)] on step uploadEventMessages, with message: uploadEventMessagesStep.
* --stacktrace:com.pwx.crs.frwk.exp.technical.CRS2UnexpectedBatchException: An unexpected exception occurred in batch job JAXB unmarshalling exception; nested exception is javax.xml.bind.UnmarshalException
Any idea what this is about?
When I slightly change the maintenanceTransaction opening tag of the input xml like this:
<MaintenanceTransaction:maintenanceTransaction xsi:type="MaintenanceTransaction:MaintenanceTransaction">
It does work. But that is not a solution as the clients won't deliver the input xml's like that. So why is the error occuring? There seems to be a problem with determining which class maintenanceTransaction is part of.
Also tried different approaches on the marshaller bean definition:
<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
<property name="contextPath" value="com.pwx.crs.informationcollection.ifw.process.model.mt"/>
</bean>
and
<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
<property name="classesToBeBound">
<list>
<value>com.pwx.crs.informationcollection.ifw.process.model.mt.ObjectFactory</value>
</list>
</property>
</bean>
The results for both are the same and similar to the first error
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02", local:"maintenanceTransaction"). Expected elements are <{http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02}AccessTokenLifecycleStatus>
... and here the whole list of classes in the package.
But the xml is perfectly valid against the xsd, as that is done beforehand. Idea's, suggestions, ... that I could try?
Here's my theory.
What mappedClass actually does is switches the unmarshalling method from unmarshaller.unmarshal(source) to unmarshaller.unmarshal(source, this.mappedClass). This is called "partial unmarshalling", i.e. you can unmarshal a known class from some element regardless of the element name.
So, as you say, this works for you. And it does not without mappedClass. This would mean that you are missing an element declaration for your root element. This also alignes well with the error you have reported:
javax.xml.bind.UnmarshalException: unexpected element
(uri:"http://www.pwx.com/Interface/CRS/MaintenanceTransaction_v02",
local:"maintenanceTransaction"). Expected elements are (none)
Which says basically the same thing.
This is also correct. As your schema does not declare a global element for the maintenanceTransaction, only for provideCommunicationEvents_BatchRequest.
So, basically you're unmarshalling the wrong element. And as your schema does not have a declaration for that, this fails. But if you specify exactly which type you want (thus providing the declaration "explicitly"), then it works.
Next, the question is, why do you you unmarshal the wrong element. My guess is that this is because you have
<property name="fragmentRootElementName" value="maintenanceTransaction"/>
This probably points the unmarshaller to use the maintenanceTransaction element as the root element. So the unmarshaller is applied to the wrong element.
Using contextPath, adding more bound classes etc. does not help as none of it adds the element declaration for the maintenanceTransaction.
Now, how to fix this. I see the following options:
Add a global element for maintenanceTransaction to your schema
(OR) Customize your schema to generate an additional #XmlRootElement for MaintenanceTransaction
(OR) Don't use the fragmentRootElementName, unmarshal provideCommunicationEvents_BatchRequest
(OR) Leave it as is with mappedClass
If you handle a specific case here, namely maintenanceTransaction then I'd use the fragmentRootElementName/mappedClass combo. Just as you do now.
So after researching through the code and debugging and much more labour I ended up with something that works. I don't exactly know why, as it was a guess, but it works.
<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.pwx.crs.informationcollection.ifw.process.model.mt.MaintenanceTransaction</value>
</list>
</property>
<property name="mappedClass" value="com.pwx.crs.informationcollection.ifw.process.model.mt.MaintenanceTransaction"/>
</bean>
As you can see it was not necessary to use the ObjectFactory as classesToBeBound (But it has to be noted that it would have worked as well but in my opinion this is more readable). The thing that really fixes it is adding a mappedClass.
From what I have seen in the code while debugging (I could not check the code f UnmarshallingImpl as this is part of rt.jar and this is not public even though it is part of the jre ...) The classesToBeBound parameter is used to create the jaxbContext for the unmarshaller and the mappedClass is passed to this unmarshaller as expectedType. But when this is not provided and the ObjectFactory is used as parameter the error clearly shows a list of expected classes and the one applicable is present ... Maybe a bug in the unmarshaller, maybe something that I don't understand. But my root problem is solved.
When creating a JAXBContext on a model generated from an XML Schema you should do one of the following to get all the necessary metadata:
Option #1 - Create it on the Generated ObjectFactory Class
<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
<property name="classesToBeBound">
<list>
<value>com.pwx.crs.informationcollection.ifw.process.model.mt.ObjectFactory</value>
</list>
</property>
</bean>
Option #2 - Create it on the Package Name(s) of the Generated Model
<bean id="maintenanceTransactionUnmarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" scope="step">
<property name="contextPath" value="com.pwx.crs.informationcollection.ifw.process.model.mt"/>
</bean>
The following is my configuration xml for a file polling functionality. I have to change the output directory sometimes.
<int-file:inbound-channel-adapter id="filesIn" directory="file:${paths.root}" channel="abc" >
<int:poller id="poller" fixed-delay="5000"/>
</int-file:inbound-channel-adapter>
<int:channel id="abc"/>
<int-file:outbound-channel-adapter channel="abc" id="filesOut"
directory-expression="#aPath.getPath()"
delete-source-files="true"
filename-generator ="filenameGenerator"/>
<bean id="filenameGenerator" class="com.dms.util.FileNameGenerator"/>
In the
#Override
public String generateFileName(Message<?> message)
{
I have tried setting the value of a configured bean property.
This is the additional configuration for that
<bean name="aPath" class="com.dms.util.GetOutPath">
<property name="path" value="${paths.destination}"/>
</bean>
paths.destination is from a property file.
In the generateFileName method I have added the code for changing the property value as follows
#Autowired
private GetOutPath outPathBean;
For the bean:
#Component("outPathBean")
and in my code
outPathBean.setPath(newFolder);
My debugging shows that the value of the property does not change. My question is, How do I modify the directory either in the generateFileName method or by any other way.
Please help!
The general mechanism you are trying to use will work because the file name generator is calle before evaluating the directory expression.
However, you have two instances of you really have the #Component defined (and you are using component scanning, you will have two instances of GetOutPath - aPath and outPathBean.
The expression is using the instance that you are not changing.
You need to inject the same bean instance that you are using in your expression.
I have an scenario where I must send messages in order to a rest service and I plan to use a resequencer. The behaviour of this resequencer must be:
Order messages by time in day (hh:mm:ss): data on the message
Release messages only after they stay a period of time in the bus (p.e. 2 minutes)
As the default Resequencer didn't serve for this purpose I decided to develop a custom one changing the ResequencerMessageGroupProcessor for a CustomResequencerMessageGroupProcessor.
I succeded using a service activator but I had to explictly define the output-channel as a property. Isn't there a way to use the output-channel attribute on the xml declaration?
When I use the output-channel attribute the following error occurs:
Caused by: java.lang.IllegalArgumentException: no outputChannel or replyChannel header available
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler.sendReplies(AbstractCorrelatingMessageHandler.java:616)
at org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler.completeGroup(AbstractCorrelatingMessageHandler.java:597)
at org.springframework.integration.aggregator.AbstractCorrelatingMessageHandler.handleMessageInternal(AbstractCorrelatingMessageHandler.java:405)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
... 46 more
here's my example:
<int:channel id="resequencerChannel"/>
<int:service-activator id="customResequencer" ref="resequencingMessageHandler"
input-channel="resequencerChannel" />
<int:channel id="aggregatedMessageChannel" />
<bean id="resequencingMessageHandler" class="org.springframework.integration.aggregator.ResequencingMessageHandler">
<constructor-arg name="releaseStrategy" ref="timeoutReleaseStrategy"/>
<constructor-arg name="processor" ref="customResequencerMessageGroupProcessor"/>
<constructor-arg name="store" ref="redisMessageStore"/>
<constructor-arg name="correlationStrategy" ref="customCorrelationStrategy"/>
<property name="outputChannel" ref="aggregatedMessageChannel"/>
<property name="sendPartialResultOnExpiry" value="true"></property>
</bean>
<bean id="customResequencerMessageGroupProcessor" class="test.resequencer.CustomResequencerMessageGroupProcessor">
<constructor-arg name="timeout" value="10000"/>
</bean>
<bean id="timeoutReleaseStrategy" class="org.springframework.integration.aggregator.TimeoutCountSequenceSizeReleaseStrategy" >
<constructor-arg name="threshold" value="100000"></constructor-arg>
<constructor-arg name="timeout" value="10000"/>
</bean>
<bean id="customCorrelationStrategy" class="org.springframework.integration.aggregator.HeaderAttributeCorrelationStrategy" >
<constructor-arg name="attributeName" value="correlationId"/>
Also, if you think there is a better way to do this, please, I would apreciatte telling so
Thanks in advance!
Regards
Guzman
When referencing (ref) a MessageHandler from a <service-activator/> the XML output-channel is only applied if the referenced handler is an AbstractReplyProducingMessageHandler (ARPMH).
Components such as routers, aggregators, resequencers, are not considered to be ARPMHs because they sometimes produce a reply, sometimes don't and, in the case of a router, might produce multiple "replies" which doesn't fit the service activator model.
We could probably refactor the aggregator/resequencer to be ARPMHs because they only produce 0 or 1 "reply". We could also add some smarts to the ServiceActivatorFactoryBean to inject the output channel if the reference is an AbstractCorrelatingMessageHandler. Feel free to open an Improvement JIRA Issue.
In the meantime, your solution is the correct work-around.
I have configured content negotiation in my Spring MVC 3 app as follows:
<bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="0" />
<property name="favorPathExtension" value="true" />
<property name="defaultContentType">
<ref bean="htmlMediaType" />
</property>
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
<property name="defaultViews">
<list>
<bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller">
<bean class="org.springframework.oxm.castor.CastorMarshaller" />
</property>
</bean>
</list>
</property>
<property name="viewResolvers">
<ref bean="tilesViewResolver" />
</property>
</bean>
This works very well -- all my views will render as html views with the 'normal' view templates, or as JSON or XML dumps of the view model data depending on the 'Accept' header.
However, this seems to be a bit of a security hole to me. Some of my actions are API-style actions, and are legitimately available in HTML or JSON or XML. However, some views are intended to be HTML-only. I don't really want end-users to be able to see all the view data just by adding ".json" to the url.
Is there any way to do content negotiation in Spring MVC, but only on actions which have explicitly opted-in to it? Can I set up a controller annotation like #RespondsTo("xml", "json")?
Why don't you use a filter through DelegatingFilterProxy to block users from accessing unnecessary content types ?
I was just facing the same problem. produces attribute of #RequestMapping helps for that. Although it's the opposite of what you asked for - kind of opt-out instead of opt-in, but I think it's what can please you.
#Controller
#RequestMapping("/categories")
public class CategoriesController
{
#RequestMapping(value = "/create", method = RequestMethod.GET, produces = "application/xhtml+xml")
public String createForm(Model model)
{
}
}
/create - works fine by displaying JSP view
/create.json - 406 Error
One way to do it would be to use Spring Security to restrict which pages can be seen based on the content-type (or whatever other method(s) you are using for content negotiation.