another is: java thread safe regarding SimpleDateFormat

We have an OMS component using camel listen and poll MQ. To enhance the performance, I increased the thread listening to the queue, from


    from(""GShareMQ:queue:Q.US_OE.IN.TRANSACTIONSCHEDULE?acknowledgementModeName=CLIENT_ACKNOWLEDGE")
        // keep the JMS message for Client acknowledge on success
        .beanRef("jmsMessageUtil", "addMessageToHeader")
        .convertBodyTo(String.class)
        .log(LoggingLevel.INFO, "Processing SSB Execution:\n${in.body}")

        .beanRef("transactionScheduleParser", "parseTransactionScheduleMessage")

to

 from(""GShareMQ:queue:Q.US_OE.IN.TRANSACTIONSCHEDULE?acknowledgementModeName=CLIENT_ACKNOWLEDGE&concurrentConsumers=10")
        // keep the JMS message for Client acknowledge on success
        .beanRef("jmsMessageUtil", "addMessageToHeader")
        .convertBodyTo(String.class)
        .log(LoggingLevel.INFO, "Processing SSB Execution:\n${in.body}")

        .beanRef("transactionScheduleParser", "parseTransactionScheduleMessage")

then on off, out of few thousand messages, 20 got some exception as


java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084)
	at java.lang.Double.parseDouble(Double.java:510)
	at java.text.DigitList.getDouble(DigitList.java:151)
	at java.text.DecimalFormat.parse(DecimalFormat.java:1303)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)
	at java.text.DateFormat.parse(DateFormat.java:335)
	at com.bfm.cpm.parser.TransactionScheduleParser.parseTransactionScheduleMessage(TransactionScheduleParser.java:145)

or

java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
	at java.lang.Long.parseLong(Long.java:431)
	at java.lang.Long.parseLong(Long.java:468)
	at java.text.DigitList.getLong(DigitList.java:177)
	at java.text.DecimalFormat.parse(DecimalFormat.java:1298)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1591)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)
	at java.text.DateFormat.parse(DateFormat.java:335)
	at com.bfm.cpm.parser.TransactionScheduleParser.parseTransactionScheduleMessage(TransactionScheduleParser.java:137)

line 137 and 145 are

        Date prvDt = prvDateFmt.parse(prvDateStr);
Date contrSettlementdate = prvDateFmt.parse(contrSettlementdateStr);

and prvDateFmt is instance variable, which is not thread safe.
http://stackoverflow.com/questions/6840803/simpledateformat-thread-safety

private SimpleDateFormat prvDateFmt = new SimpleDateFormat("yyyy-MM-dd");

When cpm parsing the message, it uses transactionScheduleParserBean as a singleton (Spring), which uses one same instance of SimpleDateFormat, and it is not threadSafe.

Date prvDt = prvDateFmt.parse(prvDateStr);
Date contrSettlementdate = prvDateFmt.parse(contrSettlementdateStr);

============================================updated on Aug 15, 2012
Solution:

I am to using the ThreadLocal class, which would provide one instance per thread.Think this should work.

here instead of using instance of SimpleDateFormat, I am to use instance of ThreadLocal as instance variable of the singleton spring bean, TransactionScheduleParser.

  private ThreadLocal<SimpleDateFormat> postingDateFmt = new ThreadLocal<SimpleDateFormat>(){
	
	  @Override
	protected SimpleDateFormat initialValue() {
		// TODO Auto-generated method stub
	       SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
		   df.setTimeZone(omsTz);
		   return df;
	}
  };
  
  private ThreadLocal<SimpleDateFormat> prvDateFmt = new ThreadLocal<SimpleDateFormat>(){
		
	  @Override
	protected SimpleDateFormat initialValue() {
		// TODO Auto-generated method stub
       SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
	   df.setTimeZone(omsTz);
	   return df;
	}
  };

while within the method, which is per thread level, instead of using the instance variable of type SimpleDateFormat, I am to use instance variable of type ThreadLocal to get one SimpleDateFormat per thread.

Date contrSettlementdate = prvDateFmt.get().parse(contrSettlementdateStr);
Date actualSettlementdate = prvDateFmt.get().parse(actualSettlementdateStr);

Addon: How ThreadLocal works:
Basically, each instance of type ThreadLocal would maintain one hashMap of Thread(threadId?) and the instance of ClassType. Invoking prvDateFmt.get() would return the instance of SimpleDateFormat corresponding to this thread.

seems been sometime away from Java: calendar date difference

I was using this

if( (new Date().getTime() - tradeDate.getTime()) > 1000 * 60 *60 * 24 * 30){
..
}

for making sure trade date not older than 3 months, which then exactly encountered the round off problem mentioned here,
http://tripoverit.blogspot.com/2007/07/java-calculate-difference-between-two.html.

should instead using calendar.before()

    java.util.Date tradeDate = ct.getActionDate();
	Date now = new Date();
	Calendar threeMonBef = Calendar.getInstance();threeMonBef.setTime(now);
	threeMonBef.add(Calendar.MONTH, -3);
	
	Calendar cal1 = Calendar.getInstance();cal1.setTime(tradeDate);

if( cal1.before(threeMonBef)){
..
}

==================================================updated Aug 15, 2012
//only date information should be maintained, instead of datetime, to avoid problem due to timezone difference.

    /**
     * Get previous Business Day of the current system date.
     * Note that this method is heavy because it calls isBusinessDay, which required Database query, multiple time.
     * So, this should be called when it is really required.
     * @param channelCtx
     * @return
     */
    private Date getPreviousBusDay(ChannelContext channelCtx) {


        // yes. I believe that GOCM v1 works only for Tokyo.
        Calendar nowInTokyo = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tokyo"));
        // for testing the situation that Date part is different between Tokyo and GMT.
//        nowInTokyo.set(Calendar.AM_PM, Calendar.AM);
//        nowInTokyo.set(Calendar.HOUR, 7);
//        nowInTokyo.set(Calendar.MINUTE, 30);

        // copy the tokyo date to GMT/PDT calender. truncate datetime part.
        // This is required to get correct result from isBusinessDay method when prev calender date is Holiday.
//        Calendar prevBusDay = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        // weblogic server in US/ewd uses PDT , local dev may use different
        Calendar prevBusDay = Calendar.getInstance(); //use the timeZone of the host
        prevBusDay.set(Calendar.DATE, nowInTokyo.get(Calendar.DATE));
        prevBusDay.set(Calendar.AM_PM, Calendar.AM);
        prevBusDay.set(Calendar.HOUR, 0);
        prevBusDay.set(Calendar.MINUTE, 0);
        prevBusDay.set(Calendar.SECOND, 0);
        prevBusDay.set(Calendar.MILLISECOND, 0);

        prevBusDay.add(Calendar.DATE, -1);
        while(!isBusinessDay(prevBusDay.getTime(), channelCtx)){
            prevBusDay.add(Calendar.DATE, -1);
        }
        return prevBusDay.getTime();
    }

camel again, csv bindy and file2 EIP

camel as a good EIP, best as far as i know, while i really only know few.

I am working on csv bindy recently for a project, to consume FTP csv file, and parse/marshal to java object then publish to MQ. camel is making EIP very very easy.

Just one point to put, which not so easy to locate through tons of internet information we googled:
crlf (the carriage return) is default to windows, and I think we need to change it to Unix for unix environment application.( Even though I guess it might work in either environment configuration. )

To make things slightly complex, if we wanna use camel default EIP functions, move, preMove and errorMove to handle archiving, inProgress and error handling, then be careful of the file name if you are going to change it.

For example, if we plan to change the file name, by appending the timestamp, make sure we don’t change the file extension. instead of

moveFailed=/error/${file:name}.${date:now:yyyyMMddHHmmssSSS} 

better use,

moveFailed=/error/${file:name.noext}-${date:now:yyyyMMddHHmmssSSS}.${file:ext}

.
thats what exactly happen for me, which camel always throw “No records found in CSV file” exception, as I have put

skipFirstLine= true

, and seems camel then confused because of file extension, and can’t recognize the crlf (carriage return).

http://camel.apache.org/file2.html

http://camel.apache.org/bindy.html