Unmarshalling MTOM message using Camel Spring-WS and JAXB dataformat - jaxb

1) Is there a way to unmarshall MTOM message using Camel Spring-WS component?
2) I tried with Camel JAXB dataformat. It didn't work. Datahandler doesn't have any content. Does JAXB dataformat support MTOM?
<dataFormats>
<jaxb id="jaxb" contextPath="com.mycompany.hr.jaxb"/>
</dataFormats>
<route>
<from uri="spring-ws:rootqname:{http://mycompany.com/hr/schemas}HolidayRequest?endpointMapping=#endpointMapping" />
<unmarshal ref="jaxb"/>
<process ref="testProcessor" />
</route>
3) I thought MTOM is not enabled in JAXB dataformat. So I created a custom dataformat using MTOM enabled JAXB2Marshaller. But still facing the same issue.
import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.transform.Source;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
public class MtomDataFormat implements DataFormat {
public void marshal(Exchange arg0, Object arg1, OutputStream arg2)
throws Exception {
// TODO Auto-generated method stub
}
public Object unmarshal(Exchange exchange, InputStream is) throws Exception {
Source source = exchange.getContext().getTypeConverter().mandatoryConvertTo(Source.class, is);
Jaxb2Marshaller mar = new Jaxb2Marshaller();
mar.setContextPath("com.mycompany.hr.jaxb");
mar.setMtomEnabled(true);
return mar.unmarshal(source);
}
}
Spring configuration
<bean id="endpointMapping"
class="org.apache.camel.component.spring.ws.bean.CamelEndpointMapping">
</bean>
<bean id="testProcessor" class="TestProcessor" />
<bean id="mtomDataFormat" class="MtomDataFormat" />
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="spring-ws:rootqname:{http://mycompany.com/hr/schemas}HolidayRequest?endpointMapping=#endpointMapping" />
<unmarshal ref="mtomDataFormat"/>
<process ref="testProcessor" />
</route>
</camelContext>

Related

Spring Integration - XML Schema validator - Dynamic schema file selection

I am trying to implement XML validation using Spring Integration <int-xml:validating-filter />. I followed the discussion in usage of spring integration's schema validator?. The problem statement is the same but with an additional parameter. Instead to hard coding the value in schema-location="xyz.xsd", rather I want to dynamically select the appropriate xsd file for respective incoming xml or DOMSource inputs.
I also followed http://forum.spring.io/forum/spring-projects/integration/121115-dynamic-schema-location-for-xml-validating-filter-component where Gary Russell mentioned:
There's no direct support for dynamic schemas, but you can provide a
custom XmlValidator using the xml-validator attribute (mutually
exclusive with schema location)
Once you've introspected your document to find the schema you wish to
validate against, simply delegate to a validator that has been
configured to validate against that schema.
You can use a XmlValidatorFactory to create each validator; see the
XmlValidatingMessageSelector for how to create a validator, once you
know the schema location
Since the comments dates back to the year 2012, is there an approach now in spring integration to validate input xml by dynamically selecting appropriate schema? If not can anyone provide an example on how to implement?
Following is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway"
default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel"
error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<!-- How to do -->
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" />
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator" />
Here is my Validator class defined:
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
#Override
public SAXParseException[] validate(Source source) throws IOException {
/* How to implement this method?
Using XPath I can identify the root node from 'source' and then load
the appropriate XSD file. But don't know how to proceed
or what should be 'return'(ed) from here.
Any example is much appreciated.
*/
return null;
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// TODO Auto-generated method stub
return null;
}
}
Which is the best way of implementing the XML validator using Spring Integration?
There have been no changes since that comment.
As I said there, your validator needs to use XmlValidatorFactory to create a validator for each schema; then call a specific validator for each message; something like:
String schema = determineSchema(source);
XmlValidator val = lookupValidatorForSchema(schema);
if (val == null) {
// create a new one and add it to the map.
}
return val.validate(source);
If it helps other folks who are trying to do the same
Based on Gary's suggestion, I have come out with an implementation of XmlValidator by dynamically identifying input XML and then selecting appropriate Schema file to apply the validation.
Below is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway" default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel" error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" /> <!-- a MessageRejectedException is thrown in case validation fails -->
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator">
<constructor-arg>
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="OTA_AirAvailRQ" value="common/schemas/FS_OTA_AirAvailRQ.xsd" />
<entry key="OTA_AirBookModifyRQ" value="common/schemas/FS_OTA_AirBookModifyRQ.xsd" />
<entry key="OTA_AirBookRQ" value="common/schemas/FS_OTA_AirBookRQ.xsd" />
</map>
</constructor-arg>
</bean>
To demonstrate I have used the OTA schema files to construct a map as constructor-arg. The map key is the root node from the XML file from the gateway and value is the location of the xsd file; and form the key-value pair map.
Refer to the below implementation class how this map is being used to identify the input XML and apply the validation.
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.springframework.xml.validation.XmlValidatorFactory;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
private static final Log LOGGER = LogFactory.getLog(DomSourceValidator.class);
private Map<String, String> schemaMap;
private static Map<String, XmlValidator> validatorMap = new HashMap<>();
public DomSourceValidator(Map<String, String> schemaMap) {
this.schemaMap = schemaMap;
}
#PostConstruct
private void init() throws IOException {
LOGGER.info("Constructing Validators from schema resource list ...");
Assert.notEmpty(schemaMap, "No schema resource map found");
if (validatorMap.isEmpty()) {
XmlValidator validator = null;
for (Map.Entry<String, String> entry : schemaMap.entrySet()) {
validator = createValidatorFromResourceUri(entry.getValue());
validatorMap.put(entry.getKey(), validator);
}
}
}
#Override
public SAXParseException[] validate(Source source) throws IOException {
Assert.notNull(schemaMap, "No validator(s) defined");
XmlValidator validator = lookupValidator(source);
return validator.validate(source);
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// Skip implementation
return null;
}
private XmlValidator lookupValidator(Source source) {
String reqType = determineRequestType(source);
LOGGER.info("Loading validator for type: " + reqType);
XmlValidator xmlValidator = validatorMap.get(reqType);
Assert.notNull(xmlValidator, "No validator found for type: " + reqType);
return xmlValidator;
}
private String determineRequestType(Source source) {
if (source instanceof DOMSource) {
return ((DOMSource) source).getNode().getFirstChild().getNodeName();
}
return null;
}
private XmlValidator createValidatorFromResourceUri(String schemaResource) throws IOException {
Assert.notNull(schemaResource);
return XmlValidatorFactory.createValidator(new ClassPathResource(schemaResource), XmlValidatorFactory.SCHEMA_W3C_XML);
}
}
As soon as the spring bean id="xmlValidator" is initialized, the #PostConstruct kicks in to create Validator instances using XmlValidatorFactory from the resource URIs to have a pre-initialized validators.
If there is a validation error, a org.springframework.integration.MessageRejectedException: Message was rejected due to XML Validation errors is thrown (ref. throw-exception-on-rejection="true" in the <int-xml:validating-filter />).
The above implementation works perfectly fine for me. One can customize it further, or post another version to achieve the same.
Note
Instead of using a <int-xml:validating-filter />, one can also use a <int:service-activator /> in the <int-chain />, as logically <int-xml:validating-filter /> does not actually do any filter logic. But it serves the purpose.

Webflow view-state model bean properties are not autowired

I have a Spring 3.2.11 and Webflow 2.4.0 app using JSF2 for view rendering.
One of my pages uses a view-state model bean for validation but when the validatePasswordChange method is called, the bean properties have not been autowired so I get a NPE. I don't understand why they're null!?
Thank you for any suggestions.
Here's my bean validation method. Why is the passwordService property null?
public void validatePasswordChange(ValidationContext context) {
MessageContext messages = context.getMessageContext();
if (!confirmPassword.equals(newPassword)) {
messages.addMessage(new MessageBuilder().error()
.source("confirmPassword")
.defaultText(
this.getMessage("kyn.password.change.validation.noMatch"))
.build());
};
List<String> validationMessages = passwordService
.validatePasswordPolicyCompliance(getUsername(),
newPassword);
if (validationMessages != null) {
for (String message : validationMessages) {
messages.addMessage(new MessageBuilder().error()
.source("confirmPassword")
.defaultText(message).build());
}
}
}
Here's a snippet from my flow.xml. I'm inserting passwordUpdateBean into the flowScope in the on-start and expecting the model attribute to use it:
<on-start>
<evaluate expression="passwordUpdateBean" result="flowScope.passwordUpdateBean"></evaluate>
</on-start>
<view-state id="passwordChange" model="passwordUpdateBean">
<transition on="proceed" to="update"/>
<transition on="cancel" to="redirectCancel" validate="false"/>
</view-state>
And here's my bean config xml:
<bean id="passwordUpdateBean"
class="com.xyz.PasswordUpdateBean"
scope="prototype"
parent="abstractWebBean">
<property name="passwordService" ref="passwordManagementService"/>
<property name="appUserDetailsService" ref="appUserDetailsService"/>
<property name="autoLoginAfterEnrollment" value="true"/>
<property name="usernamePasswordAuthenticationProvider" ref="usernamePasswordAuthenticationProvider"/>
</bean>
beans will NOT auto wire inside your flow definitions. You have get have to explicitly retrieve the bean into the flow definition.
Note #Service class annotations are automatically accessible in flow files (i.e <evaluate expression="myService.getPasswordUpdateBean()" result="flowScope.passwordUpdateBean"/>)
<on-start>
<evaluate expression="....getPasswordUpdateBean()" result="flowScope.passwordUpdateBean"></evaluate>
<!-- where '....' = either #service class or some other obj that holds a reference to the bean you seek -->
</on-start>

Apache Camel 2.14 Rest DSL Security

I would like to use the new Rest DSL in Apache Camel 2.14 to create a rest interface. I would like to use the Jetty component and I have a basic example setup like this:
Spring Security Configuration
<?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:spring-security="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://camel.apache.org/schema/spring-security http://camel.apache.org/schema/spring-security/camel-spring-security.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<spring-security:http auto-config="true" use-expressions="true" >
<spring-security:intercept-url pattern="/**" access="isFullyAuthenticated()"/>
<spring-security:http-basic></spring-security:http-basic>
</spring-security:http>
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="true"/>
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
</list>
</property>
</bean>
<spring-security:authentication-manager alias="authenticationManager">
<spring-security:authentication-provider user-service-ref="userDetailsService"/>
</spring-security:authentication-manager>
<spring-security:user-service id="userDetailsService">
<spring-security:user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN"/>
<spring-security:user name="user" password="user" authorities="ROLE_USER"/>
</spring-security:user-service>
<authorizationPolicy id="admin" access="ROLE_ADMIN"
authenticationManager="authenticationManager"
accessDecisionManager="accessDecisionManager"
xmlns="http://camel.apache.org/schema/spring-security"/>
<authorizationPolicy id="user" access="ROLE_USER"
xmlns="http://camel.apache.org/schema/spring-security"/>
Camel Route Configuration
restConfiguration().component("jetty").host("0.0.0.0").port(24999).bindingMode(RestBindingMode.json).dataFormatProperty("prettyPrint", "true");
rest("address").description("Contains services for addresses").
consumes("application/json").
produces("application/json").
get().
route().policy("admin").
to("bean:restAddressApi?method=queryAddress").endRest();
When I try to access this protected URL using wget with this:
wget --http-user=admin --http-password=admin http://localhost:24999/address/
Then I get this error in the console:
org.apache.camel.CamelAuthorizationException: Cannot find the Authentication instance.. Exchange[Message: [Body is null]]
at org.apache.camel.component.spring.security.SpringSecurityAuthorizationPolicy.beforeProcess(SpringSecurityAuthorizationPolicy.java:72)
at org.apache.camel.component.spring.security.SpringSecurityAuthorizationPolicy$AuthorizeDelegateProcess.process(SpringSecurityAuthorizationPolicy.java:120)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:416)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:118)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:80)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:191)
at org.apache.camel.component.jetty.CamelContinuationServlet.service(CamelContinuationServlet.java:150)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:684)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:503)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1086)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:429)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.handler.ResourceHandler.handle(ResourceHandler.java:406)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:370)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:494)
at org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:971)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:1033)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:644)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:235)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:696)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
at java.lang.Thread.run(Thread.java:745)
What am I missing in my config to get this working?
Two options.
You can use basic auth with jetty rest DSL. I just spent some time working with basic auth, a jetty rest DSL service, and camel 2.15. I wrote up my experiences here: http://www.mooreds.com/wordpress/archives/2065 . I found the camel-jetty wiki page a bit out of date, full code here: https://github.com/mooreds/camel-rest-jetty-auth
You can use spring security (at least with version 2.15.2 of camel and 4.0.1.RELEASE of spring security, which is what I am using). To do so, you need to make sure you create a processor class, as outlined here: http://camel.apache.org/spring-security.html#SpringSecurity-Authentication and put it in front of every route before you call the policy. You also need to create an exception handler to present a nice message to an unauthorized user--here's what I did in my java routebuilder: onException(org.apache.camel.CamelAuthorizationException.class).handled(true).transform(simple("Access Denied with the Policy of ${exception.policyId} !")).setHeader(Exchange.HTTP_RESPONSE_CODE, simple("401"));
Here is how I solved it using Apache Camel 2.15.2. I use this backend together with an AngularJS frontent where I use an httpInterceptor to add the authenitcation header.
I use JWT to store a tenantId in the authorization header. This tenantId is used together with Hibernate and its Multi Tenancy Support.
Front end login controller, using AngularJs and written in TypeScript:
/// <reference path="../reference.ts"/>
module Controllers {
export class Credentials {
username:string;
password:string;
}
export interface ILoginControllerScope extends ng.IScope {
credentials:Credentials;
login:()=>void;
}
export class LoginController {
private _scope:ILoginControllerScope;
static $inject = ['$scope', '$http', 'UserService', '$location'];
constructor($scope:ILoginControllerScope, $http:ng.IHttpService, UserService:UserService, $location:ng.ILocationService) {
this._scope = $scope;
$scope.credentials = new Credentials();
$scope.login = ()=> {
$http.post('/api/authenticate', $scope.credentials)
.success(function (data:string, status:any, headers:any, config:any) {
// Remove all " characters from string.
data = data.replace(/"/gi, '');
UserService.setSecurityToken(data);
$location.path('/');
})
.error(function (data:any, status:any, headers:any, config:any) {
});
}
}
}
}
#Override
public void process(Exchange exchange) throws Exception {
Map<String, String> credentials = exchange.getIn().getBody(HashMap.class);
String username = credentials.get("username");
String password = credentials.get("password");
// Login logic that returns object() containing user info
exchange.getIn().setBody(jwtService.generateToken(tenantConfiguration));
}
}
}
This is the class JwtService that is responsible for creating a JWT Token:
package com.me.services;
import com.me.TenantConfiguration;
import com.google.common.collect.Maps;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
#Service
public class JwtService {
public static final String TENANT_ID = "tenant_id";
public static final String ROLE = "role";
#Value("${jwt.subject}")
private String jwtSubject;
private byte[] signingKey;
private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
public SignatureAlgorithm getSignatureAlgorithm() {
return signatureAlgorithm;
}
public byte[] getSigningKey() {
if (this.signingKey == null) {
// Generate new signingKey
Random random = new SecureRandom();
signingKey = new byte[64];
random.nextBytes(signingKey);
}
return this.signingKey;
}
public String getJwtSubject() {
return jwtSubject;
}
public String generateToken(TenantConfiguration tenantConfiguration){
Map<String, Object> claims = Maps.newHashMap();
claims.put(TENANT_ID, tenantConfiguration.getSessionId());
String token = Jwts.builder().setSubject(this.getJwtSubject()).setClaims(claims).signWith(this.getSignatureAlgorithm(), this.getSigningKey()).compact();
return token;
}
}
To use this in a route I define it like this:
rest("api/messages").description("Restful API for messages").
get().id("GetMessages").route().to("springSecurityContextLoader").policy(authorizationPolicy).to("bean:restMessageApi?method=getAllMessages").endRest().
SpringSecurityContextloader
#Service
public class SpringSecurityContextLoader implements Processor {
#Inject
private JwtService jwtService;
#Override
public void process(Exchange exchange) throws Exception {
String authorization = exchange.getIn().getHeader("Authorization", String.class);
Jwt jwt = Jwts.parser().setSigningKey(jwtService.getSigningKey()).parse(authorization);
Map<String, Object> claims = (Map<String, Object>) jwt.getBody();
String tenantId = claims.get(JwtService.TENANT_ID).toString();
Authentication authentication = new PreAuthenticatedAuthenticationToken(tenantId, "something", Lists.newArrayList(new SimpleGrantedAuthority("ROLE_USER")));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
Taken from https://camel.apache.org/jetty.html
Jetty handlers and security configuration
You can configure a list of Jetty handlers on the endpoint, which can be useful for enabling advanced Jetty security features. These handlers are configured in Spring XML as follows:
<-- Jetty Security handling -->
<bean id="userRealm" class="org.mortbay.jetty.plus.jaas.JAASUserRealm">
<property name="name" value="tracker-users"/>
<property name="loginModuleName" value="ldaploginmodule"/>
</bean>
<bean id="constraint" class="org.mortbay.jetty.security.Constraint">
<property name="name" value="BASIC"/>
<property name="roles" value="tracker-users"/>
<property name="authenticate" value="true"/>
</bean>
<bean id="constraintMapping" class="org.mortbay.jetty.security.ConstraintMapping">
<property name="constraint" ref="constraint"/>
<property name="pathSpec" value="/*"/>
</bean>
<bean id="securityHandler" class="org.mortbay.jetty.security.SecurityHandler">
<property name="userRealm" ref="userRealm"/>
<property name="constraintMappings" ref="constraintMapping"/>
</bean>

Camel POJO producing with JAXB data format exception

I am using Camel 2.10.3
Here is my camel context:
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<endpoint id="webserviceStart" uri="direct:webserviceStart"/>
<dataFormats>
<jaxb id="jaxb" prettyPrint="true"
contextPath="com.jaxbtest.package" />
</dataFormats>
<route id="myRoute">
<from ref="webserviceStart" />
<marshal ref="jaxb" />
<to uri="spring-ws:http://wshost:8010/service"/>
<unmarshal ref="jaxb" />
</route>
</camelContext>
This code works:
#Component
public class WebserviceClient
{
#EndpointInject( ref = "webserviceStart" )
ProducerTemplate _producer;
public Response invoke( Request input )
{
return ( Response ) _producer.sendBody( input ).getOut().getBody();
}
}
This code (following the "Hiding the Camel APIs from your code using #Produce" section of http://camel.apache.org/pojo-producing.html) does not:
#Component
public class WebserviceClient
{
public static interface MyWebservice
{
Response invoke( #Body Request body );
}
#EndpointInject( ref = "webserviceStart" )
MyWebservice _producer;
public Response invoke( Request input )
{
return ( Response ) _producer.invoke( input );
}
}
it throws an exception:
Caused by: java.io.IOException: javax.xml.bind.JAXBException: class org.apache.camel.component.bean.BeanInvocation nor any of its super class is known to this context.
at org.apache.camel.converter.jaxb.JaxbDataFormat.marshal(JaxbDataFormat.java:103)
at org.apache.camel.processor.MarshalProcessor.process(MarshalProcessor.java:59)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:73)
If this is a known bug in camel does anyone know the issue that is related to it? Should I create a new JIRA for this? This seems to be such a simple use case of POJO producing.
Generally, when you get this error, it means you haven't set the list of classes on the JAXB context.
You'd do in JAVA DSL -
JAXBContext context = JAXBContext.newInstance('your classes');
JaxbDataFormat jaxb = new JaxbDataFormat();
jaxb.setContext(context);
and then use your custom dataformat 'jaxb' as your marshal/unmarshal ref.
Thanks!

RESTEasy + Spring + Jackson + Jaxb

I am trying to serialize JAXB annotated class with Jackson using RESTEasy. By default ResteasyJacksonProvider is configured to use JACKSON annotation only. Is there a way to configure ResteasyJacksonProvider to use JAXB annotation using spring? There are couple of programmatic ways but would prefer if there is some spring configuration.
Couple of ways I am thinking of
Use ContextResolver for ObjectMapper type to return ObjectMapper configured to use JaxbAnnotationIntrospector instead of JacksonAnnotationIntrospector.
Extend ResteasyJacksonProvider and pass JAXB annotation during construction.
Any other way?
Well the first option of using ContextResolver works but I still think there should be an easier way to do this just by some configuration.
You can get this from the configuration only, no need to program anything special.
Here's how to:
First set your configuration right, I use Jackson + JAXB, both set under ContentNegotiatingViewResolver bean:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="1"/>
<property name="mediaTypes">
<map>
<entry key="xml" value="application/xml" />
<entry key="json" value="application/json" />
</map>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller">
<oxm:jaxb2-marshaller id="marshaller">
<oxm:class-to-be-bound name="com.shay.dashboard.data.structure.page.PageObject" />
<oxm:class-to-be-bound name="com.shay.dashboard.data.structure.tab.TabObject" />
<oxm:class-to-be-bound name="com.shay.dashboard.data.structure.section.SectionObject" />
<oxm:class-to-be-bound name="com.shay.dashboard.data.structure.element.nonembedded.ElementObject"/>
<oxm:class-to-be-bound name="com.shay.dashboard.data.structure.element.embedded.EmbeddedElementObject"/>
<oxm:class-to-be-bound name="com.shay.dashboard.data.structure.chart.common.ChartManager"/>
</oxm:jaxb2-marshaller>
</property>
</bean>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>
</list>
</property>
</bean>
Notice that under the marshaller I set the oxm:class-to-be-bound - those are the classes to be bound by JAXB.
Now for the module, I used ordinary annotation package (javax.xml.bind.annotation), non marshaller specific. Jackson Json and JAXB both know how to read it.
For example:
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name="page")
public class PageObject implements ComponentTypeObject{
#XmlAttribute(name="name")
private String name;
#XmlAttribute(name="id",required=true)
private String id;
#XmlElements({#XmlElement(name="tab", type=TabXmlAdapter.class)})
private List<TabXmlAdapter> tabRef;
Finally the controller for your MVC needs to return a model and view:
#RequestMapping(value="/get_page", method = RequestMethod.GET)
public ModelAndView initPage()
{
ModelAndView mav = null;
try
{
PageObject myPage = (PageObject) Utilities.getUtilities().loadObjectFromFile(XmlComponentType.page);
mav = new ModelAndView("page","page",myPage);
}
catch (Exception e)
{
e.getMessage();
}
return mav;
}
Now while calling your URL ending with .json you'd get the JSON representation, and with .xml - and XML. Both are translated by the viewer, provided you gave the correct mapping when annotating the module.

Resources