Unable to unmarshal XML using Camel JacksonXML - spring-boot

I'm trying to simply unmarshal an XML file as below:
<?xml version="1.0" encoding = "UTF-8" ?>
<feed xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<products>
<product>
<facet type="string" elementType="string" name="Weight (g)"><![CDATA[210]]></facet>
</product>
</products>
</feed>
I've got this classes:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#XmlRootElement(name = "feed")
#XmlAccessorType(XmlAccessType.FIELD)
public class Feed {
private Products products;
}
Subclass Products:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Products {
private List<Product> products;
}
Subclass Product:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#XmlAccessorType(XmlAccessType.FIELD)
public class Product {
#XmlElement(name = "facet")
private List<Facet> facet;
}
And finally Facet:
#Getter
#Setter
#Builder
#NoArgsConstructor
#AllArgsConstructor
#XmlAccessorType(XmlAccessType.FIELD)
public class Facet {
#XmlElement(name = "name")
private String name;
#XmlElement(name = "type")
private String type;
#XmlElement(name = "elementType")
private String elementType;
private String content;
}
The camel route I've written to unmarshall is as below:
#Component
public class XMLSplitterRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("file:src/main/resources/files/xml").routeId("xmlUmarshaller")
.log("body: ${body}")
.unmarshal().jacksonXml(Products.class)
.log("The unmarshalled object is ${body}")
.marshal().json()
.to("activemq:json-marshal-queue");
}
}
But I keep getting the error below:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected non-whitespace text ('210' in Array context: should not occur (or should be handled)
at [Source: (BufferedInputStream); line: 29, column: 96] (through reference chain: com.sammy.model.Products["products"]->java.util.ArrayList[0]->com.sammy.model.Product["facet"])
and
Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected non-whitespace text ('210' in Array context: should not occur (or should be handled)
at [Source: (BufferedInputStream); line: 29, column: 96]
This means, it seems not to know what to do with the value within the cdata of the XML file! I've looked everywhere but not seen any potential solution. Please, help!!!

From the nice suggestiongs of Nicolas Filotto, I fixed my mappings by first converting my XML to XSD then generated the POJO's using xjc.
For Camel unmarshal process, I changed it from jacksonXML to use Jaxb converter.
#Component
public class XMLSplitterRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
DataFormat jaxb = new JaxbDataFormat("com.sammy.model");
from("file:src/main/resources/files/xml").routeId("xmlSplitter")
.log("body: ${body}")
.unmarshal(jaxb)
.log("The unmarshalled object is ${body}")
}
}
This now works like a charm!!!

Related

How do I search for a nested #Indexed key in Redis redis-om-spring?

I am trying to search for a nested #Indexed field using redis-om-spring
For some reason a find returns expected 1 entry for level 1, but returns unexpected 0 entries for level 2.
I am not sure if I can only search one level deep or if I am making a mistake.
#SpringBootApplication
#Configuration
#EnableRedisDocumentRepositories
#Slf4j
public class Application {
#Autowired
ProductRepository productRepository;
#Bean
CommandLineRunner loadTestData() {
return args -> {
productRepository.deleteAll();
productRepository.save(new Product(new MyKey(new MyId("A_level2Key1"),"A_level1Key1"),"FirstColour"));
productRepository.save(new Product(new MyKey(new MyId("B_level2Key1"),"B_level1Key1"),"SecondColour"));
var byMyKeyLevel1Key = productRepository.findByMyKeyLevel1Key("A_level1Key1");
System.out.println(byMyKeyLevel1Key.size());//returns expected 1 entry for level 1
var byMyKeyMyIdLevel2Key = productRepository.findByMyKeyMyIdLevel2Key("A_level2Key1");
System.out.println(byMyKeyMyIdLevel2Key.size());//returns unexpected 0 entries for level 2
};
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Product
import com.redis.om.spring.annotations.Document;
import com.redis.om.spring.annotations.Indexed;
import com.redis.om.spring.annotations.Searchable;
#Slf4j
#Data
#AllArgsConstructor
#NoArgsConstructor
#Document
public class Product {
#Indexed
#Id
private MyKey myKey;
#Searchable
#NonNull
private String colourDesc;
}
ProductRepository
public interface ProductRepository extends RedisDocumentRepository<Product, String> {
List<Product> findByMyKeyLevel1Key(String level1Key);
List<Product> findByMyKeyMyIdLevel2Key(String level2Key);
}
MyKey
import com.redis.om.spring.annotations.Indexed;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyKey {
#Indexed
MyId myId;
#Indexed
String level1Key;
}
Data in Redis:
FT.INFO "com.redis.om.documents.domain.ProductIdx"
From the logs, it generates searches:
FT.SEARCH "com.redis.om.documents.domain.ProductIdx" #myKey_level1Key:{A_level1Key1}
FT.SEARCH "com.redis.om.documents.domain.ProductIdx" #myKey_myId_level2Key:{A_level2Key1}
I start Redis with:
docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:latest
The issue is likely in the naming of the repository methods. I wrote your example as a test in the project repo, this will be using release 0.7.0 - but it should work with 0.6.x-SNAPSHOTs:
The entity:
#Data #AllArgsConstructor #NoArgsConstructor #Document public class Product {
#Indexed #Id private MyKey myKey;
#Searchable #NonNull private String colourDesc;
}
The first-level key:
#Data #AllArgsConstructor #NoArgsConstructor public class MyKey {
#Indexed MyId myId;
#Indexed String level1Key;
#Override public String toString() {
return myId.toString();
}
}
The second-level key:
#Data #AllArgsConstructor #NoArgsConstructor public class MyId {
#Indexed
private String level2Key;
#Override public String toString() {
return level2Key;
}
}
The repo, here's where things probably went wrong for you...
public interface ProductRepository extends RedisDocumentRepository<Product, MyKey> {
List<Product> findByMyKey_Level1Key(String level1Key);
List<Product> findByMyKey_MyId_Level2Key(String level2Key);
}
and here's a snippet of the test class I used:
#Autowired ProductRepository productRepository;
#BeforeEach
void setup() {
productRepository.deleteAll();
productRepository.save(new Product(new MyKey(new MyId("A_level2Key1"),"A_level1Key1"),"FirstColour"));
productRepository.save(new Product(new MyKey(new MyId("B_level2Key1"),"B_level1Key1"),"SecondColour"));
}
#Test
void testFindNestedKeyValues() {
var byMyKeyLevel1Key = productRepository.findByMyKey_Level1Key("A_level1Key1");
assertThat(byMyKeyLevel1Key).map(Product::getMyKey).map(MyKey::getLevel1Key).containsExactly("A_level1Key1");
var byMyKeyMyIdLevel2Key = productRepository.findByMyKey_MyId_Level2Key("A_level2Key1");
assertThat(byMyKeyMyIdLevel2Key).map(Product::getMyKey).map(MyKey::getMyId).map(MyId::getLevel2Key).containsExactly("A_level2Key1");
}
Hope that helps! Thanks for trying Redis OM Spring!

Instructing Sping Data MongoDB to use correct mapping between ObjectId and its class

I cannot retrieve the 2nd level nested objects in Spring Data MongoDB
I have nested collection in MongoDB to retrieve with Spring. Imagine this schema
#Data
#Builder
#Document(collection = "emitted")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Emitter{
#Id
private String id;
#Field("installation")
#DocumentReference(lazy = true)
private Installaton installation;
// other fields
#Data
#Builder
#Document(collection = "installation")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Installation {
#Id
private String id;
#Field("subject")
#DocumentReference(lazy = true)
private Subject subject;
// other fields
#Data
#Builder
#Document(collection = "subject")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Subject {
#Id
private String id;
// other fields
Plus, I have MapStruct to map nested object field to string, for the purpose of avoiding cyclic reference introducing the search by id of the collection:
#ObjectFactory
public <T> T map(#NonNull final String id, #TargetType Class<T> type) {
return mongoTemplate.findById(id, type);
}
Everything works at first level, but at nested level I have this error:
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.bson.types.ObjectId] to type [com.package.collections.Subject]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:175)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1826)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.doConvert(MappingMongoConverter.java:1818)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1337)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getPotentiallyConvertedSimpleRead(MappingMongoConverter.java:1311)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$DefaultConversionContext.convert(MappingMongoConverter.java:2371)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$ConversionContext.convert(MappingMongoConverter.java:2174)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1936)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:638)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:549)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:527)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:491)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:427)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:423)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:120)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDocumentCallback.doWith(MongoTemplate.java:3326)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:2940)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2618)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2588)
at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:922)
at com.package.myapp.services.mapper.ReferenceMapper.map(ReferenceMapper.java:26)
at com.package.myapp.services.mapper.InstallationMapperImpl.toEntity(InstallationMapperImpl.java:102)
When asking the conversion, the findById works correctly and retrieve the object and the nested one. It fails when the request is for 2nd level nested object, where the ObjectId is retrieved but cannot be converted and fails.
I'm answering myself because I found a solution suited for my problem.
I only needed the entity object with the id, so I wrote a converter:
public class ObjectIdToSubjectConverter implements Converter<ObjectId, Subject> {
#Override
public Subject convert(ObjectId source) {
return Subject.builder().id(source.toString()).build();
}
}
And register it as a bean:
#Configuration
public class MongoConfig {
#Bean
public MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.singletonList(new ObjectIdToSubjectConverter());
}
}

Spring data swagger generates requestbody field from getter method

I have this DTO which i use as the #RequestBody of an endpoint in my spring data app:
#Builder
#AllArgsConstructor
#EqualsAndHashCode
#Getter
#NoArgsConstructor
public class CreateBridgeInput {
private String customIdName;
private BridgeType type;
private String zoneId;
#Override
public String getId() {
return this.customIdName;
}
}
If I make CreateBridgeInput implement this interface
public interface EntityInputBase {
public String getId();
}
The generated swagger file includes a field called id in the expected request body, despite there being no field called id.
Why does it do this, and how do I prevent this from happening? I found that I can use the #Hidden annotation from swagger, but if a class that extends EntityInputBase does actually have an id field, it will hide it when it shouldn't.

Cannot map nested #Data with MapStruct

When trying to map nested Object using #Data and #Builder, mapStruct throw the following errors: "No read accessor found for property "profile" in target type."
#Mapper(componentModel = "spring")
public interface AuthMapper {
// only for testing mapping is working
#Mapping(target = "profile.organization", source = "organization")
RequestCreateOktaUser toEntity(Integer organization);
// only for testing mapping is working
#Mapping(target = "profile.login", source = "request.profile.email")
RequestCreateOktaUser toEntity(RequestMobilePreRegisterLocation.User request);
// Throw error "No read accessor found for property "profile" in target type" at compile time
#Mapping(target = "profile.organization", source = "organization")
#Mapping(target = "profile.login", source = "request.profile.email")
RequestCreateOktaUser toEntity(RequestMobilePreRegisterLocation.User request, Integer organization);
}
Model using Lombok simplified for simplicity
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
public class RequestCreateOktaUser {
private Profile profile;
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
public static class Profile {
private String login;
private String email;
private Integer organization;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
public class RequestMobilePreRegisterUser {
#Valid
private User user;
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
public static class User {
#Valid
private Profile profile;
#Data
#NoArgsConstructor
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#Builder
public static class Profile {
#NotEmpty
#Email
private String email;
}
}
The two first Mapping are working as expected, but when trying to combine the two the following errors is being thrown at compile time "No read accessor found for property "profile" in target type."
If someone could help me on this one I would really appreciate.
Thanks a lot,
Jonathan.
solution:
#Mapper(componentModel = "spring", builder = #Builder(disableBuilder = true))

How to solve org.springframework.core.convert.ConversionFailedException while fetching the data from DB using spring data jpa

Domain Classes
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "SAMPLE_DATA")
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonStringType.class),
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class Sample implements Serializable {
private static final long serialVersionUID = 1719868663566734198L;
#Id
private Long Id;
#Type(type = "json")
#Column(columnDefinition = "json",name = "person")
private Person personObj;
private String sampledata;
private String createdBy;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Person implements Serializable {
private static final long serialVersionUID = -5427425033242474312L;
private String firstName;
private String lastName;
}
Repository Class
#Repository
public interface SampleRepository extends JpaRepository<Sample, Long> {
#Query(value = "select s.personObj,s.sampledata from Sample s where s.Id=:Id")
List<Sample> findPersonById(Long Id);
}
To map to map JSON object types i m using
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
when I am trying to fetch the list of Sample object I am getting the following exception
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.Object[]] to type [#org.springframework.data.jpa.repository.Query com.domain.Sample] for value '[Person(firstName=abc, lastName=test)]'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.domain.Person] to type [#org.springframework.data.jpa.repository.Query com.domain.Sample]
.........
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.domain.Person] to type [#org.springframework.data.jpa.repository.Query com.domain.Sample]
You don't have to select the distinct columns. You must select the Entity:
#Repository
public interface SampleRepository extends JpaRepository<Sample, Long> {
#Query(value = "select s from Sample s where s.Id=:Id")
List<Sample> findPersonById(Long Id);
}

Resources