Spring Batch: How to get properties from subclass object from BeanWrapperFieldExtractor - java

I have an application which takes xml as input and writing down to csv.
Here is my input xml:
<Main location="ABC" date="2018-02-26" name="Default">
<Sub firstname="XYZ" lastname="PQR"/>
<Sub firstname="147" lastname="123"/>
</Main>
I have below in my itemwriter's BeanWrapperFieldExtractor:
<beans:property name="fieldExtractor">
<beans:bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<beans:property name="names" value="location,date,name"/>
</beans:bean>
</beans:property>
I'm getting correct output but I want to include firstname and lastname also and i have added below:
<beans:property name="names" value="location,date,name,sub.firstname,sub.lastname"/>
but I'm getting below Errors:
org.springframework.beans.NotReadablePropertyException: Invalid property 'sub.firstname' of bean class [com.xyz.MainClass]: Bean property 'sub.firstname' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:707)
Is there any way to extract whole Sub element from xml data of put in csv file one by one.

Having the same issue, I have found two solutions to retrieve sub properties :
I assume you are mapping your input to its related classes.
1. Using the FieldExtractor, create a custom property + getter on your class to retrieve the child property :
public class Example {
#OneToOne
private ChildExample childExample;
// add Transient if you use hibernate to avoid validation:
#Transient
private String customProperty;
// custom getter :
public String getCustomProperty() {
return childExample.getChildProperty();
}
}
Then you can configure the BeanWrapperFieldExtractor on the writer :
BeanWrapperFieldExtractor<Example> fieldExtractor = new BeanWrapperFieldExtractor<>();
fieldExtractor.setNames(new String[]{"customProperty"});
// then you configure the lineAggregator on a FileItemWriter :
DelimitedLineAggregator<Example> lineAggregator = new DelimitedLineAggregator<>();
lineAggregator.setFieldExtractor(fieldExtractor);
FlatFileItemWriter<Example> writer = new FlatFileItemWriter<>();
writer.setLineAggregator(lineAggregator);
writer.open(executionContext);
...
2. Using a custom LineAggregator and overriding the toString class method :
#Component
public class ExampleLineAggregator<Example> implements LineAggregator<Example> {
#Override
public String aggregate(Example item) {
return item.toString();
}
}
Override the toString method:
public class Example {
#OneToOne
private ChildExample childExample;
#Override
public String toString() {
return childExample.getChildProperty();
}
}
Then set the custom LineAggregator to the writer :
FlatFileItemWriter<Example> writer = new FlatFileItemWriter<>();
writer.setLineAggregator(customLineAggregator);

Related

Spring bean reference not working

I have the following bean:
package com.test;
#Component
public class Sample{
String modified = null;
#Value("${url}")
private String url;
public Sample(){
System.out.println(url );
if(baseUrl.equals(""){
throw new RuntimeException("missing");
}
else{
modified = "test"+url;
}
}
}
I have added:
<context:annotation-config />
<context:property-placeholder location="classpath:test.properties"/> & <context:component-scan base-package="com.test"/>
and trying to access above "modified" field as below
<bean id="url" class="java.lang.String">
<constructor-arg value="#{sample.modified}" />
</bean>
in my application context. But I keep getting the following error:
Field or property 'sample' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext'
Not sure why i get this error?
When Spring creates the object it uses the default constructor. It can't set the property until after it constructs it. Instead of what you have, try this to see if the value is being set.
#PostConstruct
public void init(){
System.out.println(url );
if(baseUrl.equals(""){
throw new RuntimeException("missing");
}
}
JustinKSU's answer is right. You have another option: inject value via constructor using #Autowired:
#Component
public class Sample {
#Autowired
public Sample(#Value("${url}") String url) {
System.out.println(url);
if(url.equals("") {
throw new RuntimeException("missing");
}
}
}

What's the equivalent of #Convert in hibernate hbm file?

I wrote a an attribute converter. I want to apply that in an entity. I'm following a purely XML approach so far.
I could not find an equivalent of #Convert in hbm notation.
An example would be appreciated.
When I search for this, understandably, Google returns lots of results about tools/methods on "Auto Converting hbm files to entities vice versa".
Edit:
Now I'm suspecting if there is an option in hbm file, given that this is JPA annotation.
The doc of #Convert says:
The Convert annotation is used to specify the conversion of a Basic
field or property. It is not necessary to use the Basic annotation or
corresponding XML element to specify the basic type.
I'm not entirely sure what it means. Is mixing annotation and XML a way to go in this case?
I've tried this:
public class Person {
//this is enum
private Ethnicity ethnicity;
//.....
}
public enum Ethnicity{
INDIAN("IND"),
PERSIAN("PER")
//...constructors and value field.
public String value(){
return this.value;
}
public Ethnicity fromValue(String value){
//logic for conversion
}
}
Converter:
#Converter
public class EthnicityConverter implements AttributeConverter<Ethnicity,String> {
#Override
public Ethnicity convertToEntityAttribute(String attribute) {
if ( attribute == null ) {
return null;
}
return Ethnicity.fromValue( attribute );
}
#Override
public String convertToDatabaseColumn(Ethnicity dbData) {
if ( dbData == null ) {
return null;
}
return dbData.value();
}
}
HBM File:
//....other columns
<property name="ethnicity">
<column name="ethnicity"/>
<type name="EthnicityConverter"/>
</property>
//....other columns
Edit: Corrected the converter code.
The answer from Sarvana is close - you do in fact use the type XML attribute. However, type is used to name a Hibernate Type. However there is a convention to name, instead, an AttributeConverter - simply apply the prefix converted:: to your AttributeConverter FQN. E.g.,
<property name="ethnicity">
<column name="ethnicity"/>
<type name="converted::EthnicityConverter"/>
</property>
The other option is to auto-apply the converter:
#Converter( autoApply=true)
public class EthnicityConverter implements AttributeConverter<Ethnicity,String> {
...
}
Given the converter above, so long as Hibernate knows about it, Hibernate will apply that to any attribute of type Ethnicity.
HTH
type is the equivalent xml attribute for Convert annotation.
Below is to convert to Y/N in DB and Boolean in entity.
<property name="status" column="book_status" type="yes_no" not-null="true"/>
Just replace yes_no with your custom converter class
Please see my answer at
https://stackoverflow.com/a/37914271/3344829
Official documentation
https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch06.html
Update
<property name="ethnicity" column="ethnicity" type="com.example.EthnicityConverter"/>
Update
#Converter
public class EthnicityConverter implements AttributeConverter<Ethnicity, String> {
#Override
public String convertToDatabaseColumn(Ethnicity attribute) {
// TODO return String value of enum
}
#Override
public Ethnicity convertToEntityAttribute(String dbData) {
// TODO return resolved enum from string
}
}
I too had to face this issue and i was able to resolve it.
Refer : Hibernate 5 Documentation Example 33. HBM mapping for AttributeConverter
We have to use exact class with package.
Ex :
<property name="ethnicity" column="ethnicity" type="converted::com.example.EthnicityConverter"/>

Spring Data Neo4j (SDN) Query Parameter doesn't use Converter

I want to be able to store a Set of fully qualified Class names as a property of a Node. Given this Node:
#NodeEntity
public TestNode {
Set<Class<?>> classSet;
//getters and setters here
}
And the following custom converters:
public class ClassToStringConverter implements Converter<Class<?>, String> {
#Override
public String convert(final Class<?> source) {
return source.getName();
}
}
public class StringToClassConverter implements Converter<String, Class<?>> {
#Override
public String convert(final String source) {
Class<?> returnVal = null;
try {
returnVal = Class.forName(source);
} catch (ClassNotFoundException e) { }
return returnVal;
}
}
I register the converters with Spring context as such:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="my.package.ClassToStringConverter"/>
<bean class="my.package.StringToClassConverter "/>
</set>
</property>
</bean>
Then using my repository, I can properly save and retrieve the Node, and it stores the fully qualified class name as expected using my custom converters. However, I would like to be able to query by a Class name as such in my repository:
#Query("MATCH (m:TestNode) where {0} in m.classSet return m;")
public findByClassInClassSet(Class<?> clazz);
However, this seems to convert the Class using Class.toString() instead of using my Converter. So it is searching for the String "class my.package.TestNode" instead of what the Converter correctly stored as "my.package.TestNode".
Am I missing something here or doing something wrong? How can I benefit from these converters if I can't query using the Class type?
NOTE: Please excuse any typos - this code is on a disconnected network so I couldn't copy paste. If there are any typos, I assure you that is not the problem on my actual code.
Should be fixed in the next milestone release for derived finders.
For your annotated query-method, SDN cannot know what you are referring to with your parameter, so it will not be able to convert it, ever.

pass parameter to reference with autowired

i want pass parameter to #autowired ref like
public CoreDao {
private String taskId;
private final String sql = "select ....."+getTaskId()+".....";
public CoreDao(String taskId){
if(taskId.length != 0){
this.taskId = taskId;
}else{
this.taskId = "0";
}
public getTaskId(){
return this.taskId;
}
}
xml is:
<bean id="coreDao" class="Coredao" scope="prototype">
<constructor-arg type="java.lang.String" value=""/>
</bean>
and the CoreService is
#service
CoreService implement ICoreService{
#Autowired
pirvate CoreDao;
}
and xml is
<bean id="coreService" class="CoreService" scope="prototype">
<property name="coreDao" ref="coreDao"/>
</bean>
and i want use getBean("coreService","123") to get the bean with dynamic reference of coreDao.
However,when i use getBean("coreService","123"),the exception is:
error creating bean with name "coreService" defined in file ....xml,could not resolve matching constructor (hint:specify index/type/name arguments for simple parameter to avoid ambiguities.
how could do that?thanks your help.
getBean(String, Object ...) is applicable to bean's constructors or factory methods.
Your CoreService should have CoreService(String s) constructor in order to use this method.
If you want to create many CoreService instances with different parameters, you can create a factory bean which creates all instances for you and puts them together, like
#Component
public class CoreServiceFactoryBean {
#Autowired ApplicationContext ctx;
public CoreService getBean(String param) {
CoreService coreService = ctx.getBean("coreService");
CoreDao coreDao = ctx.getBean("coreDao", parameter);
coreService.setCoreDao(coreDao);
return coreService;
}
}
This way, the logic of creating bean and using it remains separate. Using factories is pretty common to configure prototype scoped beans.

Converting configuration properties to enum values

I have a configuration file which contains this line:
login.mode=PASSWORD
and an enum
public enum LoginMode {
PASSWORD, NOT_PASSWORD, OTHER }
and a spring bean
<bean id="environment" class="a.b.c.Environment" init-method="init">
<property name="loginMode" value="${login.mode}"/>
</bean>
and of course a bean class
public class Environment {
private LoginMode loginMode;
public LoginMode getLoginMode() {
return loginMode;
}
public void setLoginMode(LoginMode loginMode) {
this.loginMode = loginMode;
}
}
How can i convert the property of the configuration file (which is a String) into the corresponding enum value of LoginMode?
EDIT: i know how to get the enum value of a string input, but the issue is another one:
If i try this:
public class Environment {
private LoginMode loginMode;
public LoginMode getLoginMode() {
return loginMode;
}
public void setLoginMode(String loginMode) {
this.loginMode = LoginMode.valueOf(loginMode);
}
}
spring is complaining about getter and setter not having the same input and output type.
Bean property 'loginMode' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
Spring automatically converts input Strings to the corresponding valueOf of the desired enum.
You can do that by
LoginMode.valueOf("someString");
LoginMode.valueOf(valueOfProperty);
EDIT:
Try using converter
http://docs.spring.io/spring/docs/3.0.0.RC2/reference/html/ch05s05.html
http://forum.spring.io/forum/spring-projects/web/83191-custom-enum-string-converters
EDIT2:
also check this:
How assign bean's property an Enum value in Spring config file?

Categories