I have a HelloWorld Java project that uses Camel to obtain a Map, and print out its contents in JSON format. The Map currently has hardcoded values, but I really want to change my code so that it uses Spring to load a sensor.properties file of nested key,value pairs into this Map.
I have another Java project I wrote that only uses Spring, and can load the sensor.properties file just fine into an Arraylist object.
However, when I try to use code from that project to load the sensor.properties in my HelloWorld project I get the following Camel error with a NPE:
Returning Map
3310 [hello.world.request.timer] ERROR org.apache.camel.processor.DefaultErrorHandler - Failed delivery for exchangeId: 4e984884-df7f-4b82-a977-f5cf4c311814. Exhausted after delivery attempt: 1 caught: java.lang.NullPointerException
java.lang.NullPointerException
at sample.SensorGenerator.getSensors(SensorGenerator.java:17)
at sample.HelloWorld.returnMap(HelloWorld.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.camel.component.bean.MethodInfo.invoke(MethodInfo.java:231)
at org.apache.camel.component.bean.MethodInfo$1.proceed(MethodInfo.java:146)
at org.apache.camel.component.bean.BeanProcessor.process(BeanProcessor.java:138)
org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:67)
at org.apache.camel.processor.DelegateProcessor.processNext(DelegateProcessor.java:53)
at org.apache.camel.processor.DelegateProcessor.proceed(DelegateProcessor.java:82)
at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:97)
at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:67)
at org.apache.camel.processor.RedeliveryErrorHandler.processExchange(RedeliveryErrorHandler.java:185)
at org.apache.camel.processor.RedeliveryErrorHandler.processErrorHandler(RedeliveryErrorHandler.java:151)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:89)
at org.apache.camel.processor.DefaultErrorHandler.process(DefaultErrorHandler.java:49)
at org.apache.camel.processor.DefaultChannel.process(DefaultChannel.java:228)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:75)
at org.apache.camel.processor.UnitOfWorkProcessor.processNext(UnitOfWorkProcessor.java:70)
at org.apache.camel.processor.DelegateProcessor.process(DelegateProcessor.java:48)
at org. apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:67)
at org.apache.camel.component.timer.TimerConsumer.sendTimerExchange(TimerConsumer.java:102)
at org.apache.camel.component.timer.TimerConsumer$1.run(TimerConsumer.java:49)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
Is there something I need to add to my applicationContext.xml to tell Camel that Spring will load my sensor.properties? Do I need to use the Spring integration component specified at http://camel.apache.org/springintegration.html ?
Here is my current ApplicationContext.xml:
<?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:camel="http://camel.apache.org/schema/spring"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean
class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
<context:component-scan base-package="sample" />
<context:annotation-config />
<camel:camelContext id="HelloWorldContext">
<!-- Add Jackson library to render Java Map into JSON -->
<camel:dataFormats>
<camel:json id="jack" library="Jackson"/>
</camel:dataFormats>
<camel:route>
<!-- sends a request to the hello world JMS queue every 10 seconds -->
<camel:from
uri="timer://hello.world.request.timer?fixedRate=true&period=10000" />
<camel:to uri="log:hello.world.request?level=INFO?showAll=true" />
<camel:bean ref="helloWorld" />
<!-- now print out the map in JSON format -->
<camel:marshal ref ="jack"/>
<camel:convertBodyTo type="java.lang.String" />
<camel:log message="${body}"/>
<!-- now log the message -->
<camel:to uri="log:hello.world.response?level=INFO?showAll=true" />
</camel:route>
</camel:camelContext>
<bean id="jms" class="org.apache.activemq.camel.component.ActiveMQComponent">
<property name="configuration" ref="jmsConfig" />
</bean>
<bean id="jmsConfig" class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="jmsConnectionFactory" />
<property name="transacted" value="false" />
<property name="concurrentConsumers" value="1" />
</bean>
<bean id="jmsConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://localhost" />
<property name="redeliveryPolicy" ref="redeliveryPolicy" />
<property name="prefetchPolicy" ref="prefetchPolicy" />
</bean>
<bean id="prefetchPolicy" class="org.apache.activemq.ActiveMQPrefetchPolicy">
<property name="queuePrefetch" value="5" />
</bean>
<bean id="redeliveryPolicy" class="org.apache.activemq.RedeliveryPolicy">
<property name="maximumRedeliveries" value="1" />
<property name="backOffMultiplier" value="2" />
<property name="initialRedeliveryDelay" value="2000" />
<property name="useExponentialBackOff" value="true" />
</bean>
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="sensorProperties" location="classpath:/sensor.properties"/>
<bean class="sample.SensorGenerator">
<property name="sourceProperties" ref="sensorProperties" />
</bean>
</beans>
Here are the four Java Classes I have (HelloWorldMain.java, HelloWorld.java, Sensor.java, and SensorGenerator.Java):
UPDATED: The issue was that I had a constructor in my HelloWorld.java calling SensorGenerator instead of using #Autowired to let Spring do it. The answer by Frederic at the bottom shows the old code Constructor. The #Autowired annotation is shown below in HelloWorld.java:
HelloWorldMain.java:
package sample;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloWorldMain {
// define context to load properties with Spring
public static void main(String[] args) throws Exception {
AbstractApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
Thread.currentThread().join();
}
}
HelloWorld.java:
package sample;
import java.util.*;
import org.apache.camel.Handler;
import org.springframework.stereotype.Service;
/**
* POJO that returns Hello World string
*
*/
#Service
public class HelloWorld {
#AutoWired
SensorGenerator sensorGenerator;
#Handler
public Map<?, ?> returnMap(){
System.out.println();
System.out.println("Returning Map");
// get the map of Sensors
Map<String,String> mySensorMap = sensorGenerator.getSensors();
// print out the Sensors in the map on the console
Set keys = mySensorMap.keySet();
for (Iterator i = keys.iterator(); i.hasNext();) {
String key = (String) i.next();
String value = (String) mySensorMap.get(key);
System.out.println("key= " + key + ", value= " + value);
}
return mySensorMap;
}
}
Sensor.java (which defines the fields I'm reading from sensor.properties):
package sample;
public class Sensor {
private String make;
private String makeDataType;
private String model;
private String modelDataType;
private String serialNumber;
private String serialNumberDataType;
private String sensorType;
private String sensorTypeDataType;
// getters and setters
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public String getMakeDataType() {
return makeDataType;
}
public void setMakeDataType(String makeDataType) {
this.makeDataType = makeDataType;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getModelDataType() {
return modelDataType;
}
public void setModelDataType(String modelDataType) {
this.modelDataType = modelDataType;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public String getSerialNumberDataType() {
return serialNumberDataType;
}
public void setSerialNumberDataType(String serialNumberDataType) {
this.serialNumberDataType = serialNumberDataType;
}
public String getSensorType() {
return sensorType;
}
public void setSensorType(String sensorType) {
this.sensorType = sensorType;
}
public String getSensorTypeDataType() {
return sensorTypeDataType;
}
public void setSensorTypeDataType(String sensorTypeDataType) {
this.sensorTypeDataType = sensorTypeDataType;
}
}
SensorGenerator.java (the class where I current hard-code the properties but want to have Spring load them from sensor.properties. If I comment out the For loop and any lines referencing sourceProperties I can get the map returned with the hard coded values just fine. That's why I suspect its some sort of Spring/Camel integration issue):
package sample;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class SensorGenerator {
private Properties sourceProperties;
// variable to increment key number for each sensor
int sensorNumber = 1;
// method to inject sensor.properties into a Map using Spring
Map<String, String> getSensors() {
Map<String, String> sensorMap = new HashMap<String, String>();
for (Object key : sourceProperties.keySet()) {
// Separate out each of the key,value pairs as an entry in the
// values array
String[] values = sourceProperties.getProperty((String) key).split(
",");
System.out.println("values array size= " + values.length);
// define string buffer that appends sensor number for each sensor's
// keys. Ex: sensor1 would have s1make, s1makeDataType, etc.
StringBuffer sensorNumberStringBuffer = new StringBuffer();
sensorNumberStringBuffer.append("s");
sensorNumberStringBuffer.append(sensorNumber);
// make and its data type (with sensor number prefix)
StringBuffer makeStringBuffer = new StringBuffer();
makeStringBuffer.append(sensorNumberStringBuffer);
makeStringBuffer.append("make");
StringBuffer makeDataTypeStringBuffer = new StringBuffer();
makeDataTypeStringBuffer.append(sensorNumberStringBuffer);
makeDataTypeStringBuffer.append("makeDataType");
// model and its data type (with sensor number prefix)
StringBuffer modelStringBuffer = new StringBuffer();
modelStringBuffer.append(sensorNumberStringBuffer);
modelStringBuffer.append("model");
StringBuffer modelDataTypeStringBuffer = new StringBuffer();
modelDataTypeStringBuffer.append(sensorNumberStringBuffer);
modelDataTypeStringBuffer.append("modelDataType");
// serialNumber and its data type (with sensor number prefix)
StringBuffer serialNumberStringBuffer = new StringBuffer();
serialNumberStringBuffer.append(sensorNumberStringBuffer);
serialNumberStringBuffer.append("serialNumber");
StringBuffer serialNumberDataTypeStringBuffer = new StringBuffer();
serialNumberDataTypeStringBuffer.append(sensorNumberStringBuffer);
serialNumberDataTypeStringBuffer.append("serialNumberDataType");
// sensorType and its data type (with sensor number prefix)
StringBuffer sensorTypeStringBuffer = new StringBuffer();
sensorTypeStringBuffer.append(sensorNumberStringBuffer);
sensorTypeStringBuffer.append("sensorType");
StringBuffer sensorTypeDataTypeStringBuffer = new StringBuffer();
sensorTypeDataTypeStringBuffer.append(sensorNumberStringBuffer);
sensorTypeDataTypeStringBuffer.append("sensorTypeDataType");
/*
put all the key,value pairs for this sensor in the sensorMap
*/
//TODO: Change all the hard coded values below to be elements
// from the values array once Spring can load spring.properties
// make and and its data type
sensorMap.put(makeStringBuffer.toString(), "DummyMake");
sensorMap.put(makeDataTypeStringBuffer.toString(), "String");
// model and and its data type
sensorMap.put(modelStringBuffer.toString(), "DummyModel");
sensorMap.put(modelDataTypeStringBuffer.toString(), "String");
// serialNumber and and its data type
sensorMap.put(serialNumberStringBuffer.toString(), "1234567890");
sensorMap.put(serialNumberDataTypeStringBuffer.toString(), "long");
// sensorType and its data type
sensorMap.put(sensorTypeStringBuffer.toString(), "DummyType");
sensorMap.put(sensorTypeDataTypeStringBuffer.toString(), "String");
// increment for next sensor
sensorNumber++;
}
return sensorMap;
}
public void setSourceProperties(Properties properties) {
this.sourceProperties = properties;
}
}
Btw: Line 17 of SensorGenerator.java as mentioned in the stack trace above is:
for (Object key : sourceProperties.keySet()) {
Here is an example sensor.properties file:
sensor1=DummySensor1:String,SensorModel1:String,1234567890:long,SensorType1:String
sensor2=DummySensor2:String,SensorModel2:String,8675309123:long,SensorType2:String
The problem is that SensorGenerator is not instantiated by spring but by your code, so the properties can never be set.
#Service
public class HelloWorld {
#Handler
public Map<?, ?> returnMap(){
SensorGenerator sensorGenerator = new SensorGenerator();
You should have the sensorGenerator be autowired in your HelloWorld service.
Related
I am using ReloadableResourceBundleMessageSource to store value list of my system. So i can use the i18N functionality
i am using multiple resources in the basenames of ReloadableResourceBundleMessageSource.
I want to pass all the localized labels of the web UI to the client (front-end) in order to be cached locally at the client.
Is there a way to load the entire keys of my resource bundles?
here is my ReloadableResourceBundleMessageSource bean definition:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:resource1</value>
<value>classpath:resource2</value>
</list>
</property>
<property name="cacheSeconds" value="60"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="useCodeAsDefaultMessage" value="true"/>
</bean>
and this is my controller that pass all the keys:
#Component
#RequestMapping("/bundle")
public class ResourceBundleController {
#Autowired
private MessageSource messageSource;
#RequestMapping(value = "/locales.js")
public ModelAndView strings(HttpServletRequest request) {
// Call the string.jsp view
return new ModelAndView("/WEB-INF/includes/locales.jsp", "keys", ??? );// HERE IS MY PROBLEM. HOW SHOULD I GET ALL THE KEYS FROM MESSAGESOURCE
}
}
and here is my the resource bundle keys for the client:
<%#page contentType="text/javascript" pageEncoding="UTF-8"%>
<%#taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%#taglib prefix="spring" uri="http://www.springframework.org/tags"%>
var messages = {};
<c:forEach var="key" items="${keys}">
messages["<spring:message text='${key}' javaScriptEscape='true'/>"] = "<spring:message code='${key}' javaScriptEscape='true' />";
</c:forEach>
Any help will be appreciated.
Updated information about spring configuration regards cache of resource bundle
You could make something like this:
Your own implementation of ReloadableResourceBundleMessageSource:
public class ExposedResourceMessageBundleSource extends ReloadableResourceBundleMessageSource {
private static final Logger LOGGER = Logger.getLogger(ExposedResourceMessageBundleSource.class);
#Override
protected Properties loadProperties(Resource resource, String fileName) throws IOException {
LOGGER.info("Load " + fileName);
return super.loadProperties(resource, fileName);
}
/**
* Gets all messages for presented Locale.
* #param locale user request's locale
* #return all messages
*/
public Properties getMessages(Locale locale){
return getMergedProperties(locale).getProperties();
}
}
Service definition to handle reasource operations:
public interface MessageResolveService extends MessageSourceAware{
String getMessage(String key, Object[] argumentsForKey, Locale locale);
Map<String,String> getMessages(Locale locale);
}
And implementation:
#Service
public class MessageResolverServiceImpl implements MessageResolveService{
private static final Logger LOGGER = Logger.getLogger(MessageResolverServiceImpl.class);
private MessageSource messageSource;
#Override
public void setMessageSource(MessageSource messageSource) {
LOGGER.info("Messages i18n injected");
this.messageSource = messageSource;
}
public String getMessage(String key, Object[] arguments, Locale locale){
String message = "";
try{
message = messageSource.getMessage(key,arguments,locale);
} catch(NoSuchMessageException e){
message = key;
LOGGER.warn("No message found: "+key);
}
return message;
}
public Map<String,String> getMessages(Locale locale){
Properties properties = ((XmlWebApplicationContext)messageSource).getBean("messageSource",
ExposedResourceMessageBundleSource.class).getMessages(locale);
Map<String,String> messagesMap = new HashMap<String,String>();
for(Map.Entry<Object,Object> entry: properties.entrySet()){
if(entry.getKey() != null && entry.getValue() != null) {
messagesMap.put(entry.getKey().toString(), entry.getValue().toString());
}
}
return messagesMap;
}
}
Spring configuration:
<bean id="messageSource" class="your.package.ExposedResourceMessageBundleSource">
<property name="basenames">
<list>
<value>classpath:yourFileName</value>
<value>classpath:yourNextFileName</value>
</list>
</property>
<property name="defaultEncoding" value="UTF-8"/>
<property name="cacheSeconds" value="10"/> //If you want reload message every couple seconds. In this case every 10 seconds.
</bean>
And your #Controller(similar to this):
#Component
#RequestMapping("/bundle")
public class ResourceBundleController {
#Autowired
private MessageResolveService messageResolveService;
#RequestMapping(value = "/locales.js")
public ModelAndView strings(HttpServletRequest request) {
// Call the string.jsp view
return new ModelAndView("/WEB-INF/includes/locales.jsp", "keys", messageResolverService.getMessages(LocaleContextHolder.getLocale()));
}
I want to create a batch using Spring batch to read from an ini file and save the data in database but when I chekced the org.springframework.batch.item.file.FlatFileItemReader class I didn't find a way to parse my data from the ini file , I tried to combine the ini4j API with the spring batch but no result
my ini file :
[Cat]
a=1
b= 2
c= 3
d= 4
e= 5
f= 6
[Cat2]
a=11
b= 21
c= 31
d= 41
e= 51
f= 61
What you can do is define your own ItemStreamReader that wraps a delegate ItemStreamReader, which is just a FlatFileItemReader that uses a PatternMatchingCompositeLineMapper as the line mapper. In your ItemStreamReader, loop to read lines from your delegate and if the line is an instance of a Property domain object then add it to a list in a Section domain object. What the PatternMatchingCompositeLineMapper allows you do do is check the line for a pattern match, and pass it to the right tokenizer and fieldSetMapper for the job.
Doing it this way will allow you to read multiple lines into one Section domain object that holds a List<Property>.
public class Section {
private String name;
private List<Property> properties;
// getters and setters
#Override
public String toString() {
StringBuilder sb = new StringBuilder(name);
for (Property prop: properties) {
sb.append("," + prop.getKey() + "=" + prop.getValue());
}
return sb.toString();
}
}
public class Property {
private String key;
private String value;
// getters and setters
}
For you custom ItemStreamReader you would do this. You can see that the reading is delegated to another reader, which you will define later
import java.util.ArrayList;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ItemStreamReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
public class IniFileItemReader implements ItemStreamReader<Object> {
private Object curItem = null;
private ItemStreamReader<Object> delegate;
#Override
public Object read() throws Exception, UnexpectedInputException,
ParseException, NonTransientResourceException {
if (curItem == null) {
curItem = (Section) delegate.read();
}
Section section = (Section) curItem;
curItem = null;
if (section != null) {
section.setProperties(new ArrayList<Property>());
while (peek() instanceof Property) {
section.getProperties().add((Property) curItem);
curItem = null;
}
}
return section;
}
private Object peek() throws Exception {
if (curItem == null) {
curItem = delegate.read();
}
return curItem;
}
public void setDelegate(ItemStreamReader<Object> delegate) {
this.delegate = delegate;
}
#Override
public void close() throws ItemStreamException {
delegate.close();
}
#Override
public void open(ExecutionContext arg0) throws ItemStreamException {
delegate.open(arg0);
}
#Override
public void update(ExecutionContext arg0) throws ItemStreamException {
delegate.update(arg0);
}
}
Then in your config you define the deleagte reader with the PatternMatchingCompositeLineMapper
<bean id="inputFile" class="org.springframework.core.io.FileSystemResource"
scope="step">
<constructor-arg value="#{jobParameters[inputFile]}"></constructor-arg>
</bean>
<bean id="sectionFileReader"
class="com.underdogdevs.springbatch.reader.IniFileItemReader">
<property name="delegate" ref="trueSectionFileReader"></property>
</bean>
<bean id="trueSectionFileReader"
class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="lineMapper">
<bean
class="org.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapper">
<property name="tokenizers">
<map>
<entry key="[*" value-ref="sectionLineTokenizer">
</entry>
<entry key="*" value-ref="propertyLineTokenizer"></entry>
</map>
</property>
<property name="fieldSetMappers">
<map>
<entry key="[*" value-ref="sectionFieldSetMapper">
</entry>
<entry key="*" value-ref="propertyFieldSetMapper">
</entry>
</map>
</property>
</bean>
</property>
<property name="resource" ref="inputFile"></property>
</bean>
<bean id="sectionLineTokenizer"
class="com.underdogdevs.springbatch.tokenizer.SectionLineTokenizer">
</bean>
<bean id="sectionFieldSetMapper"
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="section"></property>
</bean>
<bean id="section" class="com.underdogdevs.springbatch.domain.Section"
scope="prototype">
</bean>
<bean id="propertyLineTokenizer"
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="delimiter" value="="></property>
<property name="names" value="key,value"></property>
</bean>
<bean id="propertyFieldSetMapper"
class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="property"></property>
</bean>
<bean id="property" class="com.underdogdevs.springbatch.domain.Property"
scope="prototype">
</bean>
You see that I also used a custom LineTozenizer. I probably could've just used a DelimitedLineTokenizer, but by the time I realized it, I had already defined the class
import org.springframework.batch.item.file.transform.DefaultFieldSetFactory;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.batch.item.file.transform.FieldSetFactory;
import org.springframework.batch.item.file.transform.LineTokenizer;
public class SectionLineTokenizer implements LineTokenizer {
private final String nameField = "name";
private final FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();
#Override
public FieldSet tokenize(String line) {
String name = line.replaceAll("\\[", "").replaceAll("\\]", "").trim();
return fieldSetFactory.create(new String[] { name },
new String[] { nameField });
}
}
Using the following writer and job
<bean id="outputFile"
class="org.springframework.core.io.FileSystemResource" scope="step">
<constructor-arg value="#{jobParameters[outputFile]}"></constructor-arg>
</bean>
<bean id="outputFileWriter"
class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" ref="outputFile"></property>
<property name="lineAggregator">
<bean
class="org.springframework.batch.item.file.transform.PassThroughLineAggregator">
</bean>
</property>
</bean>
<batch:step id="outputStep">
<batch:tasklet>
<batch:chunk commit-interval="10" reader="sectionFileReader"
writer="outputFileWriter">
<batch:streams>
<batch:stream ref="sectionFileReader" />
<batch:stream ref="trueSectionFileReader" />
</batch:streams>
</batch:chunk>
</batch:tasklet>
</batch:step>
<batch:job id="iniJob">
<batch:step id="step1" parent="outputStep"></batch:step>
</batch:job>
And using this ini file
[Cat]
a=1
b=2
c=3
d=4
e=5
f=6
[Cat2]
a=11
b=21
c=31
d=41
e=51
f=61
I get the following output, which is the format in my toString() of the Section class
Cat,a=1,b=2,c=3,d=4,e=5,f=6
Cat2,a=11,b=21,c=31,d=41,e=51,f=61
Here's the error I'm receiving.
Caused by: java.lang.IllegalStateException: Cannot convert value of type [code.ProductFieldSetMapper] to required type [org.springframework.batch.item.file.mapping.FieldSetMapper] for property 'FieldSetMapper': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:264)
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:450)
... 23 more
Here's my context file (FileReaderConfig.xml)
<?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:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch.xsd">
<bean id="reader" class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource" value="file:./output.txt" />
<property name="linesToSkip" value="1" />
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="PRODUCT_ID,NAME,DESCRIPTION,PRICE" />
</bean>
</property>
<property name="fieldSetMapper">
<bean class="code.ProductFieldSetMapper" />
</property>
</bean>
</property>
</bean>
<job id="importProducts" xmlns="http://www.springframework.org/schema/batch">
<step id="readWriteProducts">
<tasklet>
<chunk reader="reader" writer="writer" commit-interval="100" />
</tasklet>
</step>
</job>
Here's the interface (FieldSetMapper.java)
package code;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
public interface FieldSetMapper<T> {
T mapFieldSet(FieldSet fieldSet) throws BindException;
}
Here's ProductFieldSetMapper.java
package code;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
public class ProductFieldSetMapper implements FieldSetMapper<Product> {
public Product mapFieldSet(FieldSet fieldSet) throws BindException {
// TODO Auto-generated method stub
Product product = new Product();
product.setId(fieldSet.readString("PRODUCT_ID"));
product.setName(fieldSet.readString("NAME"));
product.setDescription(fieldSet.readString("DESCRIPTION"));
product.setPrice(fieldSet.readBigDecimal("PRICE"));
return product;
}
}
And here's the class that I'm running (Runner.java)
package code;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.validation.BindException;
public class Runner {
public static void main(String[] args) throws BeansException, BindException {
// TODO Auto-generated method stub
Product product;
ApplicationContext context =
new ClassPathXmlApplicationContext("FileReaderConfig.xml");
ProductFieldSetMapper obj = (ProductFieldSetMapper) context.getBean("FieldSetMapper");
product = (Product) obj.mapFieldSet((FieldSet)context.getBean("lineTokenizer"));
System.out.println(product.getDescription() + ""+product.getId()+""+product.getName());
}
}
I don't see where (or why for that matter)my code is attempting to convert a ProductFieldSetMapper into a FieldSetMapper (which is just an interface, I understand that won't work).
BTW, Product.java is a POJO with variables and their respective setters and getters.
The error was the result of me using my own interface rather than the one provided by Spring. I deleted my interface class and had ProductFieldSetMapper implement org.springframework.batch.item.file.mapping.FieldSetMapper after importing it. That solved the issue.
ProductFieldSetMapper obj =
(ProductFieldSetMapper) context.getBean("FieldSetMapper");
Should be
ProductFieldSetMapper obj =
(ProductFieldSetMapper) context.getBean("fieldSetMapper");
See your bean declaration.
<property name="fieldSetMapper">
<bean class="code.ProductFieldSetMapper" />
</property>
Here is code with some correction:
Runner.java (use DelimitedLineTokenizer class to tokenize a comma separated string into FieldSet that is further used to map it with an object (Product) via ProductFieldSetMapper class)
ApplicationContext context = new ClassPathXmlApplicationContext(
"FileReaderConfig.xml");
ProductFieldSetMapper obj = (ProductFieldSetMapper) context.getBean("fieldSetMapper");
DelimitedLineTokenizer tokenizer = (DelimitedLineTokenizer) context
.getBean("lineTokenizer");
FieldSet fieldSet = tokenizer.tokenize("1,Pepsi,Cold drinks,30");
Product product = (Product) obj.mapFieldSet(fieldSet);
System.out.println(product.getDescription() + "-" + product.getId() + "-"
+ product.getName());
Config xml file: (No need to declare any beans or jobs other than two defined below because you are not using it anywhere in you Main class)
<bean id="lineTokenizer"
class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="PRODUCT_ID,NAME,DESCRIPTION,PRICE" />
</bean>
<bean id="fieldSetMapper" class="com.spring.batch.domain.ProductFieldSetMapper" />
ProductFieldSetMapper.java: (There is no need to define your custom FieldSetMapper)
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
public class ProductFieldSetMapper implements org.springframework.batch.item.file.mapping.FieldSetMapper<Product> {
public Product mapFieldSet(FieldSet fieldSet) throws BindException {
Product product = new Product();
product.setId(fieldSet.readString("PRODUCT_ID"));
product.setName(fieldSet.readString("NAME"));
product.setDescription(fieldSet.readString("DESCRIPTION"));
product.setPrice(fieldSet.readBigDecimal("PRICE"));
return product;
}
}
For a detailed sample please read it HERE with extra functionality using spring batch jobs.
i created a DAO (ForecastPersistorDao.java) which is the interface and its corresponding implementation (ForecastPersistorDaoImpl.java). The implementation has a method 'persist()' whose job is to persist some data into the database. When I try to call persist to persist the data, it throws a NullPointerException since it does not initialize SessionFactory properly. Please help me point out what I might be doing wrong:
ForecastPersistorDao.java
public interface ForecastPersistorDao {
void persist(List<ForecastedDemand> forecastedDemands);
List<DemandForecast> retrieveLastForecast(String marketplaceId);
}
ForecastPersistorDaoImpl.java
#Repository("forecastPersistorDao")
public class ForecastPersistorDaoImpl implements ForecastPersistorDao {
private SessionFactory sessionFactory;
#Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* Persist forecast in the database for later
*
* #param forecastedDemands
* List of forecast for all asin:marketplaceId tuple
*/
#Transactional
#Override
public void persist(List<ForecastedDemand> forecastedDemands) {
System.out.println("THIS IS ALWAYS NULL-------->>>>>>>> " + sessionFactory);
Session session = sessionFactory.getCurrentSession();
Date forecastCalculationDate = new Date();
// For each [asin:marketplace] tuple
for (ForecastedDemand forecastedDemand : forecastedDemands) {
String asin = forecastedDemand.getAsinMarketplaceId().getAsin();
String marketplaceId = forecastedDemand.getAsinMarketplaceId().getMarketplaceId();
String forecastingModel = forecastedDemand.getForecastingModel();
SortedMap<Instant, Double> forecast = forecastedDemand.getForecast();
// for each forecast date - write an entry in demand_forecasts table
for (Map.Entry<Instant, Double> entry : forecast.entrySet()) {
Date dateOfForecast = entry.getKey().toDate();
double quantityForecasted = entry.getValue();
DemandForecast forecastToPersist = new DemandForecast(asin, marketplaceId, forecastCalculationDate,
forecastingModel, quantityForecasted, dateOfForecast);
session.save(forecastToPersist);
}
}
}
Main class (Runner.java):
public final class FbsRunner {
private ForecastPersistorDao forecastPersistorDao;
public static void main(String[] args) {
Runner runner = new Runner();
runner.run();
}
public void run() {
ApplicationContext context =
new FileSystemXmlApplicationContext("spring-configuration/application-config.xml");
forecastPersistorDao = (ForecastPersistorDao) context.getBean("forecastPersistorDao"); // this works fine
System.out.println(">>>>>>>>>> forecastPersistorDao [this is fine (not null)]: " + forecastPersistorDao);
List<ForecastedDemand> forecastedDemand = [a list of Forecasted demand to be persisted int he DB]
// THE CALL BELOW FAILS...
forecastPersistorDao.persist(forecastedDemands);
System.out.println("Persisted demand in the database"); // We don't reach here.
}
}
spring-configuration/application-config.xml
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
">
<!-- The main application context spring configuration -->
<import resource="application/hibernate.xml" />
<import resource="common/hibernate.xml" />
<!--<import resource="application/proxies.xml" />-->
<!--<import resource="common/aggregators.xml" /> -->
<import resource="application/environment.xml" />
<!--
Add any beans specific to your application here
-->
<bean id="forecastPersistorDao" class="com.amazon.fresh.fbs.dao.ForecastPersistorDaoImpl" />
</beans>
application/hibernate.xml:
<bean id="SessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
parent="AbstractSessionFactory" depends-on="EnvironmentHelper" >
<property name="hibernateProperties">
...
...
everything as expected
...
</bean>
common/hibernate.xml:
<beans ... >
bean id="AbstractSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
abstract="true">
<property name="mappingResources">
<list>
<value>com/amazon/fresh/fbs/dao/hibernate/RtipData.hbm.xml</value>
<value>com/amazon/fresh/fbs/dao/hibernate/Vendor.hbm.xml</value>
<value>com/amazon/fresh/fbs/dao/hibernate/AdjustmentPeriod.hbm.xml</value>
<value>com/amazon/fresh/fbs/dao/hibernate/DemandForecast.hbm.xml</value>
</list>
</property>
<property name="exposeTransactionAwareSessionFactory">
<value>true</value>
</property>
</bean>
<!-- Use Spring transactions for Hibernate -->
<tx:annotation-driven transaction-manager="txManager" mode='proxy' proxy-target-class='true'/>
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="SessionFactory" />
</bean>
<aop:aspectj-autoproxy/>
</beans>
Please try to add <context:component-scan base-package="YOUR PACKAGE NAME" /> into your application-config.xml. This will tell Spring to scan for annotated components that will be auto-registered as Spring beans.
"YOUR PACKAGE NAME" should be the package name to scan for annotated components.
sessionFactory didn't autowired correctly ,add "#component" for ForecastPersistorDaoImpl and add "context:component-scan" in application-config.xml to tell spring to initialize.
Spring has a very handy convenience class called PropertyPlaceholderConfigurer, which takes a standard .properties file and injects values from it into your bean.xml config.
Does anyone know of a class which does exactly the same thing, and integrates with Spring in the same way, but accepts XML files for the config. Specifically, I'm thinking of Apache digester-style config files. It would be easy enough to do this, I'm just wondering if anyone already has.
Suggestions?
I just tested this, and it should just work.
PropertiesPlaceholderConfigurer contains a setPropertiesPersister method, so you can use your own subclass of PropertiesPersister. The default PropertiesPersister already supports properties in XML format.
Just to show you the fully working code:
JUnit 4.4 test case:
package org.nkl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#ContextConfiguration(locations = { "classpath:/org/nkl/test-config.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
public class PropertyTest {
#Autowired
private Bean bean;
#Test
public void testPropertyPlaceholderConfigurer() {
assertNotNull(bean);
assertEquals("fred", bean.getName());
}
}
The spring config file test-config.xml
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
">
<context:property-placeholder
location="classpath:/org/nkl/properties.xml" />
<bean id="bean" class="org.nkl.Bean">
<property name="name" value="${org.nkl.name}" />
</bean>
</beans>
The XML properties file properties.xml - see here for description of usage.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="org.nkl.name">fred</entry>
</properties>
And finally the bean:
package org.nkl;
public class Bean {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Hope this helps...
Found out that Spring Modules provide integration between Spring and Commons Configuration, which has a hierarchial XML configuration style. This ties straight into PropertyPlaceholderConfigurer, which is exactly what I wanted.
Been trying to come up with a nice solution to this myself that
Revolves around creating an XSD for the config file - since in my mind the whole benefit of using XML is that you can strongly type the config file, in terms of datatypes, and which fields are mandatory/optional
Will validate the XML against the XSD, so if a value is missing it'll throw an error out rather than your bean being injected with a 'null'
Doesn't rely on spring annotations (like #Value - in my mind that's giving beans knowledge about their container + relationship with other beans, so breaks IOC)
Will validate the spring XML against the XSD, so if you try to reference an XML field not present in the XSD, it'll throw out an error too
The bean has no knowledge that its property values are being injected from XML (i.e. I want to inject individual properties, and not the XML object as a whole)
What I came up with is as below, apologies this is quite long winded, but I like it as a solution since I believe it covers everything. Hopefully this might prove useful to someone. Trivial pieces first:
The bean I want property values injected into:
package com.ndg.xmlpropertyinjectionexample;
public final class MyBean
{
private String firstMessage;
private String secondMessage;
public final String getFirstMessage ()
{
return firstMessage;
}
public final void setFirstMessage (String firstMessage)
{
this.firstMessage = firstMessage;
}
public final String getSecondMessage ()
{
return secondMessage;
}
public final void setSecondMessage (String secondMessage)
{
this.secondMessage = secondMessage;
}
}
Test class to create the above bean and dump out the property values it got:
package com.ndg.xmlpropertyinjectionexample;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main
{
public final static void main (String [] args)
{
try
{
final ApplicationContext ctx = new ClassPathXmlApplicationContext ("spring-beans.xml");
final MyBean bean = (MyBean) ctx.getBean ("myBean");
System.out.println (bean.getFirstMessage ());
System.out.println (bean.getSecondMessage ());
}
catch (final Exception e)
{
e.printStackTrace ();
}
}
}
MyConfig.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:myconfig="http://ndg.com/xmlpropertyinjectionexample/config" targetNamespace="http://ndg.com/xmlpropertyinjectionexample/config">
<xsd:element name="myConfig">
<xsd:complexType>
<xsd:sequence>
<xsd:element minOccurs="1" maxOccurs="1" name="someConfigValue" type="xsd:normalizedString" />
<xsd:element minOccurs="1" maxOccurs="1" name="someOtherConfigValue" type="xsd:normalizedString" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Sample MyConfig.xml file based on the XSD:
<?xml version="1.0" encoding="UTF-8"?>
<config:myConfig xmlns:config="http://ndg.com/xmlpropertyinjectionexample/config">
<someConfigValue>First value from XML file</someConfigValue>
<someOtherConfigValue>Second value from XML file</someOtherConfigValue>
</config:myConfig>
Snippet of pom.xml file to run xsd2java (wasn't much else in here besides setting to Java 1.6, and spring-context dependency):
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<executions>
<execution>
<id>main-xjc-generate</id>
<phase>generate-sources</phase>
<goals><goal>generate</goal></goals>
</execution>
</executions>
</plugin>
Now the spring XML itself. This creates a schema/validator, then uses JAXB to create an unmarshaller to create a POJO from the XML file, then uses spring # annotation to inject property values by quering the POJO:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd" >
<!-- Set up schema to validate the XML -->
<bean id="schemaFactory" class="javax.xml.validation.SchemaFactory" factory-method="newInstance">
<constructor-arg value="http://www.w3.org/2001/XMLSchema"/>
</bean>
<bean id="configSchema" class="javax.xml.validation.Schema" factory-bean="schemaFactory" factory-method="newSchema">
<constructor-arg value="MyConfig.xsd"/>
</bean>
<!-- Load config XML -->
<bean id="configJaxbContext" class="javax.xml.bind.JAXBContext" factory-method="newInstance">
<constructor-arg>
<list>
<value>com.ndg.xmlpropertyinjectionexample.config.MyConfig</value>
</list>
</constructor-arg>
</bean>
<bean id="configUnmarshaller" class="javax.xml.bind.Unmarshaller" factory-bean="configJaxbContext" factory-method="createUnmarshaller">
<property name="schema" ref="configSchema" />
</bean>
<bean id="myConfig" class="com.ndg.xmlpropertyinjectionexample.config.MyConfig" factory-bean="configUnmarshaller" factory-method="unmarshal">
<constructor-arg value="MyConfig.xml" />
</bean>
<!-- Example bean that we want config properties injected into -->
<bean id="myBean" class="com.ndg.xmlpropertyinjectionexample.MyBean">
<property name="firstMessage" value="#{myConfig.someConfigValue}" />
<property name="secondMessage" value="#{myConfig.someOtherConfigValue}" />
</bean>
</beans>
I'm not sure about the Apache digester-style config files, but I found a solution that was not that hard to implement and suitable for my xml config-file.
You can use the normal PropertyPlaceholderConfigurer from spring, but to read your custom config you have to create your own PropertiesPersister, where you can parse the xml (with XPath) and set the required properties yourself.
Here's a small example:
First create your own PropertiesPersister by extending the default one:
public class CustomXMLPropertiesPersister extends DefaultPropertiesPersister {
private XPath dbPath;
private XPath dbName;
private XPath dbUsername;
private XPath dbPassword;
public CustomXMLPropertiesPersister() throws JDOMException {
super();
dbPath = XPath.newInstance("//Configuration/Database/Path");
dbName = XPath.newInstance("//Configuration/Database/Filename");
dbUsername = XPath.newInstance("//Configuration/Database/User");
dbPassword = XPath.newInstance("//Configuration/Database/Password");
}
public void loadFromXml(Properties props, InputStream is)
{
Element rootElem = inputStreamToElement(is);
String path = "";
String name = "";
String user = "";
String password = "";
try
{
path = ((Element) dbPath.selectSingleNode(rootElem)).getValue();
name = ((Element) dbName.selectSingleNode(rootElem)).getValue();
user = ((Element) dbUsername.selectSingleNode(rootElem)).getValue();
password = ((Element) dbPassword.selectSingleNode(rootElem)).getValue();
}
catch (JDOMException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
props.setProperty("db.path", path);
props.setProperty("db.name", name);
props.setProperty("db.user", user);
props.setProperty("db.password", password);
}
public Element inputStreamToElement(InputStream is)
{
...
}
public void storeToXml(Properties props, OutputStream os, String header)
{
...
}
}
Then inject the CustomPropertiesPersister to the PropertyPlaceholderConfigurer in the application context:
<beans ...>
<bean id="customXMLPropertiesPersister" class="some.package.CustomXMLPropertiesPersister" />
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_FALLBACK" />
<property name="location" value="file:/location/of/the/config/file" />
<property name="propertiesPersister" ref="customXMLPropertiesPersister" />
</bean>
</beans>
After that you can use your properties like this:
<bean id="someid" class="some.example.class">
<property name="someValue" value="$(db.name)" />
</bean>