|
Hi
I found a serious issue related to the order in which the method Class<?>.getClasses() returns the Class<?>[]. Let me explain the story. First, I was running OfBiz in debug mode under Eclipse and testing the demo application, this exception raised: 2012-06-13 10:09:07,015 (http-bio-0.0.0.0-8443-exec-9) [ GenericEntity.java:426:WARN ] ---- exception report ---------------------------------------------------------- =-=-=-=-=-=-=-=-= Database type warning GenericEntity.set =-=-=-=-=-=-=-=-= In entity field [PartyContactMech.thruDate] set the value passed in [java.util.Date] is not compatible with the Java type of the field [java.sql.Timestamp] Exception: java.lang.Exception Message: Location of database type warning ---- stack trace --------------------------------------------------------------- java.lang.Exception: Location of database type warning org.ofbiz.entity.GenericEntity.set(GenericEntity.java:426) org.ofbiz.entity.GenericEntity.put(GenericEntity.java:1418) org.ofbiz.entity.GenericEntity.put(GenericEntity.java:1) javax.el.MapELResolver.setValue(MapELResolver.java:286) javax.el.CompositeELResolver.setValue(CompositeELResolver.java:329) org.ofbiz.base.util.string.UelUtil$ExtendedCompositeResolver.setValue(UelUtil.java:337) org.ofbiz.base.util.string.JuelConnector$ExtendedAstDot.setValue(JuelConnector.java:127) de.odysseus.el.tree.impl.ast.AstEval.setValue(AstEval.java:87) de.odysseus.el.TreeValueExpression.setValue(TreeValueExpression.java:146) org.ofbiz.base.util.string.UelUtil.setValue(UelUtil.java:104) org.ofbiz.base.util.collections.FlexibleMapAccessor.put(FlexibleMapAccessor.java:170) org.ofbiz.minilang.method.envops.Now.exec(Now.java:100) org.ofbiz.minilang.SimpleMethod.runSubOps(SimpleMethod.java:308) org.ofbiz.minilang.method.conditional.CompareFieldCondition.exec(CompareFieldCondition.java:135) org.ofbiz.minilang.SimpleMethod.runSubOps(SimpleMethod.java:308) The point is that the entity field PartyContactMech.thruDate is expecting a java.sql.Timestamp while it received a java.util.Date. fine. Here the analysis of the problem 1/ At one point in time, the Now static contructor executes the code displayed below: this.converter = Converters.getConverter(Long.class, targetClass); whith targetClass set to java.sql.Timestamp Class<?> targetClass = null; try { if (!this.type.isEmpty()) { targetClass = ObjectType.loadClass(this.type); } if (targetClass == null) { targetClass = java.sql.Timestamp.class; } this.converter = Converters.getConverter(Long.class, targetClass); } catch (ClassNotFoundException e) { throw new ValidationException(e.getMessage(), simpleMethod, element); } 2/ Converters.getConverter(Long.class, java.sql.Timestamp.class); is looking at line 82 as show below in the static field convertedMap a potential converter with the following key: "java.lang.Long->java.sql.Timestamp" OUTER: do { Converter<?, ?> result = converterMap.get(key); // line 82 if (result != null) { return UtilGenerics.cast(result); } if (noConversions.contains(key)) { throw new ClassNotFoundException("No converter found for " + key); } for (Converter<?, ?> value : converterMap.values()) { // line 89 if (value.canConvert(sourceClass, targetClass)) { //line 90 converterMap.putIfAbsent(key, value); // line 91 continue OUTER; } } 3/ Under Eclipse, the converterMap.get(key) initialy does not find any key. Thus the loop line 89 starts to execute. At line 90, the value DateTimeConverter.NumberToDate.canConvert(Long.class, Timestamp .class) returns true which makes the line 91 inserting the value DateTimeConverter.NumberToDate for the key "java.lang.Long->java.sql.Timestamp" 4/ Upon restarting at the OTUER label, the statement result = compertedMap.get("java.lang.Long->java.sql.Timestamp") return DateTimeConverter.NumberToDate as result. 5/ Later on when the statement this.fieldFma.put(methodContext.getEnvMap(), this.converter.convert(System.currentTimeMillis())); from the method below executes, one gets the exception above "In entity field [PartyContactMech.thruDate] set the value passed in [java.util.Date] is not compatible w ith the Java type of the field [java.sql.Timestamp]" because the type of the converted value is a java.util.Date and not java.sql.Timestamp. At that point, what's make me mad is that this same code is running well when ofbiz is started with the ant start command. Thus I looked further in the analysis and the problem is coming from this fact: The Eclipse compiler produces a DateTimeConverters.class from which the method DateTimeConverters.getClasses() returns the inner classes sorted by lexical order of their name The Sun SDK 1.6.0_30 javac produces a DateTimeConverters.class from which the method DateTimeConverters.getClasses() returns the inner classes sorted by the reverse lexical order of their name Let me explain this point. I tested with this simple program both compiler's generated classe for DateTimeConverter public class Main { public static void main(String[] args) { DateTimeConverters dateTimeConverters; Class<?>[] classes = DateTimeConverters.class.getClasses(); for (Class<?> cls : classes) { System.out.println(cls); } } Compiling the DateTimeConverter with the Eclipse compiler, the above program produces class org.ofbiz.base.conversion.DateTimeConverters$CalendarToDate class org.ofbiz.base.conversion.DateTimeConverters$CalendarToLong class org.ofbiz.base.conversion.DateTimeConverters$CalendarToString class org.ofbiz.base.conversion.DateTimeConverters$CalendarToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$DateToCalendar class org.ofbiz.base.conversion.DateTimeConverters$DateToLong class org.ofbiz.base.conversion.DateTimeConverters$DateToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$DateToString class org.ofbiz.base.conversion.DateTimeConverters$DateToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$DurationToBigDecimal class org.ofbiz.base.conversion.DateTimeConverters$DurationToDouble class org.ofbiz.base.conversion.DateTimeConverters$DurationToFloat class org.ofbiz.base.conversion.DateTimeConverters$DurationToList class org.ofbiz.base.conversion.DateTimeConverters$DurationToLong class org.ofbiz.base.conversion.DateTimeConverters$DurationToSet class org.ofbiz.base.conversion.DateTimeConverters$DurationToString class org.ofbiz.base.conversion.DateTimeConverters$GenericLocalizedConverter class org.ofbiz.base.conversion.DateTimeConverters$LongToCalendar class org.ofbiz.base.conversion.DateTimeConverters$NumberToDate class org.ofbiz.base.conversion.DateTimeConverters$NumberToDuration class org.ofbiz.base.conversion.DateTimeConverters$NumberToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$NumberToSqlTime class org.ofbiz.base.conversion.DateTimeConverters$NumberToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToDate class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToList class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToSet class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToString class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToTime class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToList class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToSet class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToString class org.ofbiz.base.conversion.DateTimeConverters$StringToCalendar class org.ofbiz.base.conversion.DateTimeConverters$StringToDate class org.ofbiz.base.conversion.DateTimeConverters$StringToDuration class org.ofbiz.base.conversion.DateTimeConverters$StringToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$StringToSqlTime class org.ofbiz.base.conversion.DateTimeConverters$StringToTimeZone class org.ofbiz.base.conversion.DateTimeConverters$StringToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$TimeZoneToString class org.ofbiz.base.conversion.DateTimeConverters$TimestampToDate class org.ofbiz.base.conversion.DateTimeConverters$TimestampToList class org.ofbiz.base.conversion.DateTimeConverters$TimestampToSet class org.ofbiz.base.conversion.DateTimeConverters$TimestampToSqlDate while compiling DateTimeConverters with the Sun SDK, the above program produces class org.ofbiz.base.conversion.DateTimeConverters$TimeZoneToString class org.ofbiz.base.conversion.DateTimeConverters$TimestampToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$TimestampToSet class org.ofbiz.base.conversion.DateTimeConverters$TimestampToList class org.ofbiz.base.conversion.DateTimeConverters$TimestampToDate class org.ofbiz.base.conversion.DateTimeConverters$StringToTimeZone class org.ofbiz.base.conversion.DateTimeConverters$StringToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$StringToSqlTime class org.ofbiz.base.conversion.DateTimeConverters$StringToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$StringToDuration class org.ofbiz.base.conversion.DateTimeConverters$StringToDate class org.ofbiz.base.conversion.DateTimeConverters$StringToCalendar class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToString class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToSet class org.ofbiz.base.conversion.DateTimeConverters$SqlTimeToList class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToTime class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToString class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToSet class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToList class org.ofbiz.base.conversion.DateTimeConverters$SqlDateToDate class org.ofbiz.base.conversion.DateTimeConverters$NumberToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$NumberToSqlTime class org.ofbiz.base.conversion.DateTimeConverters$NumberToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$NumberToDuration class org.ofbiz.base.conversion.DateTimeConverters$NumberToDate class org.ofbiz.base.conversion.DateTimeConverters$LongToCalendar class org.ofbiz.base.conversion.DateTimeConverters$GenericLocalizedConverter class org.ofbiz.base.conversion.DateTimeConverters$DurationToString class org.ofbiz.base.conversion.DateTimeConverters$DurationToSet class org.ofbiz.base.conversion.DateTimeConverters$DurationToLong class org.ofbiz.base.conversion.DateTimeConverters$DurationToList class org.ofbiz.base.conversion.DateTimeConverters$DurationToFloat class org.ofbiz.base.conversion.DateTimeConverters$DurationToDouble class org.ofbiz.base.conversion.DateTimeConverters$DurationToBigDecimal class org.ofbiz.base.conversion.DateTimeConverters$DateToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$DateToString class org.ofbiz.base.conversion.DateTimeConverters$DateToSqlDate class org.ofbiz.base.conversion.DateTimeConverters$DateToLong class org.ofbiz.base.conversion.DateTimeConverters$DateToCalendar class org.ofbiz.base.conversion.DateTimeConverters$CalendarToTimestamp class org.ofbiz.base.conversion.DateTimeConverters$CalendarToString class org.ofbiz.base.conversion.DateTimeConverters$CalendarToLong class org.ofbiz.base.conversion.DateTimeConverters$CalendarToDate Hu... How could it be that such lexical order or reverse order makes OfBiz fails or not??? Here the explanation The Converters class initialy loads the convertedMap with this method public static void loadContainedConverters(Class<?> containerClass) { // This only returns -public- classes and interfaces for (Class<?> clz: containerClass.getClasses()) { try { // non-abstract, which means no interfaces or abstract classes if ((clz.getModifiers() & Modifier.ABSTRACT) == 0) { Object value; try { value = clz.getConstructor().newInstance(); } catch (NoSuchMethodException e) { // ignore this, as this class might be some other helper class, // with a non-pubilc constructor continue; } if (value instanceof ConverterLoader) { ConverterLoader loader = (ConverterLoader) value; loader.loadConverters(); } } } catch (Exception e) { Debug.logError(e, module); } } } you can note that the main loop is driven by the statement: for (Class<?> clz: containerClass.getClasses()) which returns either the lexically ordered inner classes with Eclipse compiler or the reversed lexically ordered inner classes with the Sun SDK. This means that the convertedMap will be initialized with the same order (direct or reversed) Thus returning back to the Now static contructor statement which calls Converters.getConverter(Long.class, Timestamp.class), the statement convertedMap.values() will return 1/ In case of the Sun SDk, the converter DateTimeConverters$NumberToTimestamp which leads to the correct expected java type java.sql.Timestamp 2/ In case of the Eclipse compiler, the converter DateTimeConverters$NumberToDate which leads to the incorrect unexpected java type java.util.Date. Conclusion: The behavior of Converters.getConverter(souceClass, targetClass) is depending of the order in which the compiler is generating the DateTimeConverters inner classes to be returned by the getClasses() method. Proposal for solving this issue Each time the method Class<?>.getClasses() is called, add a additional step before to sort the resulting array of inner classes in the lexically reversed order as done by the Sun SDK. Rgds Francis ANDRE |
|
Thank you for the detailed report. I will look into it.
-Adrian On 6/13/2012 4:27 PM, Francis ANDRE wrote: Hi |
|
This should be fixed in rev 1350081. Let me know if it works.
-Adrian On 6/14/2012 2:21 AM, Adrian Crum wrote: Thank you for the detailed report. I will look into it. |
|
Yeap it works... verified with the latest trunk....
Thanks Le 14/06/2012 05:34, Adrian Crum a écrit : This should be fixed in rev 1350081. Let me know if it works. |
| Free forum by Nabble | Edit this page |
