This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/struts.git


The following commit(s) were added to refs/heads/master by this push:
     new 7e912742e Basic LocalDateTime support (#532)
7e912742e is described below

commit 7e912742edd55c77cec37a9fbfbe077b683ebf83
Author: gregh3269 <gregh3...@gmail.com>
AuthorDate: Tue May 17 06:31:30 2022 +0100

    Basic LocalDateTime support (#532)
    
    * Basic LocalDateTime support
    
    * Basic LocalDateTime support
    
    * Basic LocalDateTime support
    
    * Basic LocalDateTime support
    
    * Basic LocalDateTime support
    
    Co-authored-by: Greg Huber <ghu...@guestales.co.uk>
    Co-authored-by: Greg Huber <ghu...@apache.org>
---
 .../xwork2/conversion/impl/DateConverter.java      | 117 +++++++++++++++------
 .../conversion/impl/XWorkBasicConverter.java       |   3 +
 .../xwork2/interceptor/ParametersInterceptor.java  |   2 +-
 .../xwork2/conversion/impl/DateConverterTest.java  |  41 ++++++++
 4 files changed, 130 insertions(+), 33 deletions(-)

diff --git 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java
index 99c1e2f62..18068d334 100644
--- 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java
+++ 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java
@@ -18,28 +18,34 @@
  */
 package com.opensymphony.xwork2.conversion.impl;
 
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.struts2.conversion.TypeConversionException;
-import com.opensymphony.xwork2.ActionContext;
-import com.opensymphony.xwork2.TextProvider;
-import com.opensymphony.xwork2.util.ValueStack;
-
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Member;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.TemporalAccessor;
 import java.util.Date;
 import java.util.Locale;
 import java.util.Map;
 
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.conversion.TypeConversionException;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.TextProvider;
+import com.opensymphony.xwork2.util.ValueStack;
+
 public class DateConverter extends DefaultTypeConverter {
 
     private final static Logger LOG = 
LogManager.getLogger(DateConverter.class);
 
     @Override
-    public Object convertValue(Map<String, Object> context, Object target, 
Member member, String propertyName, Object value, Class toType) {
+    public Object convertValue(Map<String, Object> context, Object target, 
Member member, String propertyName,
+            Object value, Class toType) {
         Date result = null;
 
         if (value instanceof String && ((String) value).length() > 0) {
@@ -52,15 +58,12 @@ public class DateConverter extends DefaultTypeConverter {
             } else if (java.sql.Timestamp.class == toType) {
                 Date check = null;
                 SimpleDateFormat dtfmt = (SimpleDateFormat) 
DateFormat.getDateTimeInstance(DateFormat.SHORT,
-                        DateFormat.MEDIUM,
-                        locale);
-                SimpleDateFormat fullfmt = new 
SimpleDateFormat(dtfmt.toPattern() + MILLISECOND_FORMAT,
-                        locale);
+                        DateFormat.MEDIUM, locale);
+                SimpleDateFormat fullfmt = new 
SimpleDateFormat(dtfmt.toPattern() + MILLISECOND_FORMAT, locale);
 
-                SimpleDateFormat dfmt = (SimpleDateFormat) 
DateFormat.getDateInstance(DateFormat.SHORT,
-                        locale);
+                SimpleDateFormat dfmt = (SimpleDateFormat) 
DateFormat.getDateInstance(DateFormat.SHORT, locale);
 
-                SimpleDateFormat[] fmts = {fullfmt, dtfmt, dfmt};
+                SimpleDateFormat[] fmts = { fullfmt, dtfmt, dfmt };
                 for (SimpleDateFormat fmt : fmts) {
                     try {
                         check = fmt.parse(sa);
@@ -85,8 +88,33 @@ public class DateConverter extends DefaultTypeConverter {
                     } catch (ParseException ignore) {
                     }
                 }
+            } else if (java.time.LocalDateTime.class == toType) {
+                DateTimeFormatter dtf = null;
+                TemporalAccessor check;
+                DateTimeFormatter[] dfs = 
getDateTimeFormats(ActionContext.of(context), locale);
+
+                for (DateTimeFormatter df1 : dfs) {
+                    try {
+                        check = df1.parse(sa);
+                        dtf = df1;
+                        if (check != null) {
+                            break;
+                        }
+                    } catch (DateTimeParseException ignore) {
+                    }
+                }
+                if (dtf == null) {
+                    throw new TypeConversionException("Could not parse date");
+                } else {
+                    try {
+                        return LocalDateTime.parse(sa, dtf);
+                    } catch (DateTimeParseException e) {
+                        throw new TypeConversionException("Could not parse 
date", e);
+                    }
+                }
             }
-            //final fallback for dates without time
+
+            // final fallback for dates without time
             if (df == null) {
                 df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
             }
@@ -95,15 +123,17 @@ public class DateConverter extends DefaultTypeConverter {
                 result = df.parse(sa);
                 if (!(Date.class == toType)) {
                     try {
-                        Constructor<?> constructor = toType.getConstructor(new 
Class[]{long.class});
-                        return constructor.newInstance(new 
Object[]{Long.valueOf(result.getTime())});
+                        Constructor<?> constructor = toType.getConstructor(new 
Class[] { long.class });
+                        return constructor.newInstance(new Object[] { 
Long.valueOf(result.getTime()) });
                     } catch (Exception e) {
-                        throw new TypeConversionException("Couldn't create 
class " + toType + " using default (long) constructor", e);
+                        throw new TypeConversionException(
+                                "Couldn't create class " + toType + " using 
default (long) constructor", e);
                     }
                 }
             } catch (ParseException e) {
                 throw new TypeConversionException("Could not parse date", e);
             }
+
         } else if (Date.class.isAssignableFrom(value.getClass())) {
             result = (Date) value;
         }
@@ -111,16 +141,16 @@ public class DateConverter extends DefaultTypeConverter {
     }
 
     /**
-     * The user defined global date format,
-     * see {@link org.apache.struts2.components.Date#DATETAG_PROPERTY}
+     * The user defined global date format, see
+     * {@link org.apache.struts2.components.Date#DATETAG_PROPERTY}
      *
      * @param context current ActionContext
-     * @param locale current Locale to convert to
-     * @return defined global format
+     * 
+     * @return defined global date string format
      */
-    protected DateFormat getGlobalDateFormat(ActionContext context, Locale 
locale) {
+    protected String getGlobalDateString(ActionContext context) {
         final String dateTagProperty = 
org.apache.struts2.components.Date.DATETAG_PROPERTY;
-        SimpleDateFormat globalDateFormat = null;
+        String globalDateString = null;
 
         final TextProvider tp = findProviderInStack(context.getValueStack());
 
@@ -130,23 +160,29 @@ public class DateConverter extends DefaultTypeConverter {
             // is the same as input = DATETAG_PROPERTY
             if (globalFormat != null && !dateTagProperty.equals(globalFormat)) 
{
                 LOG.debug("Found \"{}\" as \"{}\"", dateTagProperty, 
globalFormat);
-                globalDateFormat = new SimpleDateFormat(globalFormat, locale);
+                globalDateString = globalFormat;
             } else {
                 LOG.debug("\"{}\" has not been defined, ignoring it", 
dateTagProperty);
             }
         }
 
-        return globalDateFormat;
+        return globalDateString;
     }
 
     /**
      * Retrieves the list of date formats to be used when converting dates
+     * 
      * @param context the current ActionContext
-     * @param locale the current locale of the action
+     * @param locale  the current locale of the action
      * @return a list of DateFormat to be used for date conversion
      */
     private DateFormat[] getDateFormats(ActionContext context, Locale locale) {
-        DateFormat globalDateFormat = getGlobalDateFormat(context, locale);
+        DateFormat globalDateFormat = null;
+        String globalFormat = getGlobalDateString(context);
+        //
+        if (globalFormat != null) {
+            globalDateFormat = new SimpleDateFormat(globalFormat, locale);
+        }
 
         DateFormat dt1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, 
DateFormat.LONG, locale);
         DateFormat dt2 = DateFormat.getDateTimeInstance(DateFormat.SHORT, 
DateFormat.MEDIUM, locale);
@@ -156,20 +192,37 @@ public class DateConverter extends DefaultTypeConverter {
         DateFormat d2 = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
         DateFormat d3 = DateFormat.getDateInstance(DateFormat.LONG, locale);
 
-        DateFormat rfc3339         = new 
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+        DateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
         DateFormat rfc3339dateOnly = new SimpleDateFormat("yyyy-MM-dd");
 
         final DateFormat[] dateFormats;
 
         if (globalDateFormat == null) {
-            dateFormats = new DateFormat[]{dt1, dt2, dt3, rfc3339, d1, d2, d3, 
rfc3339dateOnly};
+            dateFormats = new DateFormat[] { dt1, dt2, dt3, rfc3339, d1, d2, 
d3, rfc3339dateOnly };
         } else {
-            dateFormats = new DateFormat[]{globalDateFormat, dt1, dt2, dt3, 
rfc3339, d1, d2, d3, rfc3339dateOnly};
+            dateFormats = new DateFormat[] { globalDateFormat, dt1, dt2, dt3, 
rfc3339, d1, d2, d3, rfc3339dateOnly };
         }
 
         return dateFormats;
     }
 
+    /**
+     * Retrieves the list of date time formats to be used when converting dates
+     * 
+     * @param context the current ActionContext
+     * @param locale  the current locale of the action
+     * 
+     * @return a list of DateTimeFormatter to be used for date conversion
+     */
+    protected DateTimeFormatter[] getDateTimeFormats(ActionContext context, 
Locale locale) {
+
+        DateTimeFormatter df1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+
+        final DateTimeFormatter[] dateFormats = new DateTimeFormatter[] { df1 
};
+
+        return dateFormats;
+    }
+
     private TextProvider findProviderInStack(ValueStack stack) {
         // TODO: ValueStack will never be null, this is just a workaround for 
tests
         if (stack == null) {
diff --git 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
index d415a8d1a..3c9eb3ad1 100644
--- 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
+++ 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
@@ -25,6 +25,7 @@ import com.opensymphony.xwork2.inject.Inject;
 import org.apache.struts2.StrutsConstants;
 
 import java.lang.reflect.Member;
+import java.time.LocalDateTime;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Date;
@@ -99,6 +100,8 @@ public class XWorkBasicConverter extends 
DefaultTypeConverter {
             result = doConvertToArray(context, o, member, propertyName, value, 
toType);
         } else if (Date.class.isAssignableFrom(toType)) {
             result = doConvertToDate(context, value, toType);
+        } else if (LocalDateTime.class.isAssignableFrom(toType)) {
+            result = doConvertToDate(context, value, toType);
         } else if (Calendar.class.isAssignableFrom(toType)) {
             result = doConvertToCalendar(context, value);
         } else if (Collection.class.isAssignableFrom(toType)) {
diff --git 
a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
 
b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
index 69b717c6c..781a8f3e5 100644
--- 
a/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
+++ 
b/core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java
@@ -201,7 +201,7 @@ public class ParametersInterceptor extends 
MethodFilterInterceptor {
             ReflectionContextState.setReportingConversionErrors(context, true);
 
             //keep locale from original context
-            
newStack.getActionContext().withLocale(stack.getActionContext().getLocale());
+            
newStack.getActionContext().withLocale(stack.getActionContext().getLocale()).withValueStack(stack);
         }
 
         boolean memberAccessStack = newStack instanceof MemberAccessValueStack;
diff --git 
a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java
 
b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java
index 774958365..fa954661e 100644
--- 
a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java
+++ 
b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/DateConverterTest.java
@@ -28,6 +28,7 @@ import org.apache.struts2.conversion.TypeConversionException;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.text.DateFormat;
+import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Locale;
@@ -43,6 +44,9 @@ public class DateConverterTest extends StrutsInternalTestCase 
{
     private final static String DATE_STR = "2020-03-20";
     private final static String DATE_CONVERTED = "Fri Mar 20 00:00:00";
     private final static String INVALID_DATE = "99/99/2010";
+    private final static String LOCALDATETIME_STR = 
"2020-03-20T00:00:00.000000";
+    private final static String LOCALDATETIME_CONVERTED = "2020-03-20T00:00";
+    private final static String INVALID_LOCALDATETIME = "2010-99-99T00:00";
     private final static String MESSAGE_PARSE_ERROR = "Could not parse date";
     private final static String MESSAGE_DEFAULT_CONSTRUCTOR_ERROR = "Couldn't 
create class null using default (long) constructor";
 
@@ -118,6 +122,43 @@ public class DateConverterTest extends 
StrutsInternalTestCase {
         }
     }
 
+    public void testLocalDateTimeType() {
+        DateConverter converter = new DateConverter();
+
+        Map<String, String> map = new HashMap<>();
+        map.put(org.apache.struts2.components.Date.DATETAG_PROPERTY, 
"yyyy-MM-dd");
+        ValueStack stack = new StubValueStack();
+        stack.push(new StubTextProvider(map));
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(new Locale("es_MX", "MX"))
+            .withValueStack(stack);
+
+        Object value = converter.convertValue(context.getContextMap(), null, 
null, null, LOCALDATETIME_STR, LocalDateTime.class);
+        assertTrue(value.toString().startsWith(LOCALDATETIME_CONVERTED));
+    }
+
+    public void testLocalDateTimeTypeConversionExceptionWhenParseError() {
+        DateConverter converter = new DateConverter();
+
+        Map<String, String> map = new HashMap<>();
+        map.put(org.apache.struts2.components.Date.DATETAG_PROPERTY, 
"yyyy-MM-dd");
+        ValueStack stack = new StubValueStack();
+        stack.push(new StubTextProvider(map));
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(new Locale("es_MX", "MX"))
+            .withValueStack(stack);
+
+        try {
+            converter.convertValue(context.getContextMap(), null, null, null, 
INVALID_LOCALDATETIME, LocalDateTime.class);
+            fail("TypeConversionException expected - Conversion error 
occurred");
+        } catch (Exception ex) {
+            assertEquals(TypeConversionException.class, ex.getClass());
+            assertEquals(MESSAGE_PARSE_ERROR, ex.getMessage());
+        }
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();

Reply via email to