WW-4799 Adding struts.date.format as a conversion format for DateConverter
diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
index 678a830..4d73813 100644
--- a/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
+++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionProxy.java
@@ -147,17 +147,13 @@
         ActionContext nestedContext = ActionContext.getContext();
         ActionContext.bind(invocation.getInvocationContext());
 
-        String retCode = null;
-
         try {
-            retCode = invocation.invoke();
+            return invocation.invoke();
         } finally {
             if (cleanupContext) {
                 ActionContext.bind(nestedContext);
             }
         }
-
-        return retCode;
     }
 
 
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 749b08b..99c1e2f 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,7 +18,12 @@
  */
 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;
@@ -31,11 +36,13 @@
 
 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) {
         Date result = null;
 
-        if (value instanceof String && value != null && ((String) value).length() > 0) {
+        if (value instanceof String && ((String) value).length() > 0) {
             String sa = (String) value;
             Locale locale = getLocale(context);
 
@@ -66,7 +73,8 @@
                 }
             } else if (java.util.Date.class == toType) {
                 Date check;
-                DateFormat[] dfs = getDateFormats(locale);
+                DateFormat[] dfs = getDateFormats(ActionContext.of(context), locale);
+
                 for (DateFormat df1 : dfs) {
                     try {
                         check = df1.parse(sa);
@@ -87,7 +95,7 @@
                 result = df.parse(sa);
                 if (!(Date.class == toType)) {
                     try {
-                        Constructor constructor = toType.getConstructor(new Class[]{long.class});
+                        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);
@@ -102,7 +110,44 @@
         return result;
     }
 
-    private DateFormat[] getDateFormats(Locale locale) {
+    /**
+     * 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
+     */
+    protected DateFormat getGlobalDateFormat(ActionContext context, Locale locale) {
+        final String dateTagProperty = org.apache.struts2.components.Date.DATETAG_PROPERTY;
+        SimpleDateFormat globalDateFormat = null;
+
+        final TextProvider tp = findProviderInStack(context.getValueStack());
+
+        if (tp != null) {
+            String globalFormat = tp.getText(dateTagProperty);
+            // if tp.getText can not find the property then the returned string
+            // is the same as input = DATETAG_PROPERTY
+            if (globalFormat != null && !dateTagProperty.equals(globalFormat)) {
+                LOG.debug("Found \"{}\" as \"{}\"", dateTagProperty, globalFormat);
+                globalDateFormat = new SimpleDateFormat(globalFormat, locale);
+            } else {
+                LOG.debug("\"{}\" has not been defined, ignoring it", dateTagProperty);
+            }
+        }
+
+        return globalDateFormat;
+    }
+
+    /**
+     * 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
+     * @return a list of DateFormat to be used for date conversion
+     */
+    private DateFormat[] getDateFormats(ActionContext context, Locale locale) {
+        DateFormat globalDateFormat = getGlobalDateFormat(context, locale);
+
         DateFormat dt1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, locale);
         DateFormat dt2 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale);
         DateFormat dt3 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
@@ -114,7 +159,29 @@
         DateFormat rfc3339         = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
         DateFormat rfc3339dateOnly = new SimpleDateFormat("yyyy-MM-dd");
 
-        return new DateFormat[]{dt1, dt2, dt3, rfc3339, d1, d2, d3, rfc3339dateOnly};
+        final DateFormat[] dateFormats;
+
+        if (globalDateFormat == null) {
+            dateFormats = new DateFormat[]{dt1, dt2, dt3, rfc3339, d1, d2, d3, rfc3339dateOnly};
+        } else {
+            dateFormats = new DateFormat[]{globalDateFormat, dt1, dt2, dt3, rfc3339, d1, d2, d3, rfc3339dateOnly};
+        }
+
+        return dateFormats;
+    }
+
+    private TextProvider findProviderInStack(ValueStack stack) {
+        // TODO: ValueStack will never be null, this is just a workaround for tests
+        if (stack == null) {
+            LOG.warn("ValueStack is null, won't be able to find TextProvider!");
+            return null;
+        }
+        for (Object o : stack.getRoot()) {
+            if (o instanceof TextProvider) {
+                return (TextProvider) o;
+            }
+        }
+        return null;
     }
 
 }
diff --git a/core/src/test/java/com/opensymphony/xwork2/StubTextProvider.java b/core/src/test/java/com/opensymphony/xwork2/StubTextProvider.java
new file mode 100644
index 0000000..bc9b95c
--- /dev/null
+++ b/core/src/test/java/com/opensymphony/xwork2/StubTextProvider.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+public class StubTextProvider implements TextProvider {
+
+    private final Map<String, String> map;
+
+    public StubTextProvider(final Map<String, String> map) {
+        this.map = map;
+    }
+
+    @Override
+    public boolean hasKey(final String key) {
+        return map.containsKey(key);
+    }
+
+    @Override
+    public String getText(final String key) {
+        return map.get(key);
+    }
+
+    @Override
+    public String getText(final String key, final String defaultValue) {
+        final String text = this.getText(key);
+        return text == null? defaultValue : text;
+    }
+
+    @Override
+    public String getText(final String key, final String defaultValue, final String obj) {
+        return this.getText(key, defaultValue);
+    }
+
+    @Override
+    public String getText(final String key, final List<?> args) {
+        return this.getText(key);
+    }
+
+    @Override
+    public String getText(final String key, final String[] args) {
+        return this.getText(key);
+    }
+
+    @Override
+    public String getText(final String key, final String defaultValue, final List<?> args) {
+        return this.getText(key);
+    }
+
+    @Override
+    public String getText(final String key, final String defaultValue, final String[] args) {
+        return this.getText(key);
+    }
+
+    @Override
+    public String getText(final String key, final String defaultValue, final List<?> args, final ValueStack stack) {
+        return this.getText(key, defaultValue);
+    }
+
+    @Override
+    public String getText(final String key, final String defaultValue, final String[] args, final ValueStack stack) {
+        return this.getText(key, defaultValue);
+    }
+
+    @Override
+    public ResourceBundle getTexts(final String bundleName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ResourceBundle getTexts() {
+        throw new UnsupportedOperationException();
+    }
+
+}
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 157e6d8..7749583 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
@@ -19,8 +19,11 @@
 package com.opensymphony.xwork2.conversion.impl;
 
 import com.opensymphony.xwork2.ActionContext;
-import org.apache.struts2.conversion.TypeConversionException;
+import com.opensymphony.xwork2.StubTextProvider;
+import com.opensymphony.xwork2.StubValueStack;
+import com.opensymphony.xwork2.util.ValueStack;
 import org.apache.struts2.StrutsInternalTestCase;
+import org.apache.struts2.conversion.TypeConversionException;
 
 import java.sql.Time;
 import java.sql.Timestamp;
@@ -31,88 +34,102 @@
 import java.util.Map;
 
 public class DateConverterTest extends StrutsInternalTestCase {
-	
-	private String INPUT_TIME_STAMP_STR;
-	private String INPUT_WHEN_LONG_CONSTRUCTOR_STR;
-	private Locale mxLocale = new Locale("es_MX", "MX");
-	private final static String RES_TIME_STAMP_STR = "2020-03-20 00:00:00.0";
-	private final static String TIME_01_59_10 = "01:59:10 AM";
-	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 MESSAGE_PARSE_ERROR = "Could not parse date";
-	private final static String MESSAGE_DEFAULT_CONSTRUCTOR_ERROR = "Couldn't create class null using default (long) constructor";
-	
-	public void testSqlTimeType() {
-		DateConverter converter = new DateConverter();
-		
-		Map<String, Object> context = new HashMap<>();
-		context.put(ActionContext.LOCALE, new Locale("es_MX", "MX"));
-		
-		Object value = converter.convertValue(context, null, null, null, TIME_01_59_10, Time.class);
-		assertEquals("01:59:10", value.toString());
-	}
-	
-	public void testSqlTimestampType() {
-		DateConverter converter = new DateConverter();
-		Map<String, Object> context = new HashMap<>();
-		context.put(ActionContext.LOCALE, mxLocale);
-		
-		Object value = converter.convertValue(context, null, null, null, INPUT_TIME_STAMP_STR, Timestamp.class);
-		assertEquals(RES_TIME_STAMP_STR, value.toString());
-	}
-	
-	public void testDateType() {
-		DateConverter converter = new DateConverter();
-		
-		Map<String, Object> context = new HashMap<>();
-		context.put(ActionContext.LOCALE, new Locale("es_MX", "MX"));
-		
-		Object value = converter.convertValue(context, null, null, null, DATE_STR, Date.class);
-		assertTrue(((Date) value).toString().startsWith(DATE_CONVERTED));
-	}
-	
-	public void testTypeConversionExceptionWhenParseError() {
-		DateConverter converter = new DateConverter();
-		
-		Map<String, Object> context = new HashMap<>();
-		context.put(ActionContext.LOCALE, new Locale("es_MX", "MX"));
-		
-		try {
-			Object value = converter.convertValue(context, null, null, null, INVALID_DATE, Date.class);
-			fail("TypeConversionException expected - Conversion error occurred");
-		} catch (Exception ex) {
-			assertEquals(TypeConversionException.class, ex.getClass());
-			assertEquals(MESSAGE_PARSE_ERROR, ex.getMessage());
-		}
-	}
-	
-	public void testTypeConversionExceptionWhenUsingLongConstructor() {
-		DateConverter converter = new DateConverter();
-		
-		Map<String, Object> context = new HashMap<>();
-		context.put(ActionContext.LOCALE, new Locale("es_MX", "MX"));
-		
-		try {
-			Object value = converter.convertValue(context, null, null, null, INPUT_WHEN_LONG_CONSTRUCTOR_STR, null);
-			fail("TypeConversionException expected - Error using default (long) constructor");
-		} catch (Exception ex) {
-			assertEquals(TypeConversionException.class, ex.getClass());
-			assertEquals(MESSAGE_DEFAULT_CONSTRUCTOR_ERROR, ex.getMessage());
-		}
-	}
-	
-	@Override
-	protected void setUp() {
-		//Due to JEP 252: Use CLDR Locale Data by Default
-		DateFormat dFormat = DateFormat.getDateInstance(DateFormat.SHORT, mxLocale);
-		if(dFormat.format(new Date()).contains("-")){ 			// Format when Java 9 or greater
-			INPUT_TIME_STAMP_STR = "2020-03-20 00:00:00.000";
-			INPUT_WHEN_LONG_CONSTRUCTOR_STR = "2020-03-20";
-		}else{ 																							// Format when Java 8 or lower
-			INPUT_TIME_STAMP_STR = "03/20/2020 00:00:00.000";
-			INPUT_WHEN_LONG_CONSTRUCTOR_STR = "03/31/20";
-		}
-	}
-	
+
+    private String INPUT_TIME_STAMP_STR;
+    private String INPUT_WHEN_LONG_CONSTRUCTOR_STR;
+    private final Locale mxLocale = new Locale("es_MX", "MX");
+    private final static String RES_TIME_STAMP_STR = "2020-03-20 00:00:00.0";
+    private final static String TIME_01_59_10 = "01:59:10 AM";
+    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 MESSAGE_PARSE_ERROR = "Could not parse date";
+    private final static String MESSAGE_DEFAULT_CONSTRUCTOR_ERROR = "Couldn't create class null using default (long) constructor";
+
+    public void testSqlTimeType() {
+        DateConverter converter = new DateConverter();
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(mxLocale);
+
+        Object value = converter.convertValue(context.getContextMap(), null, null, null, TIME_01_59_10, Time.class);
+        assertEquals("01:59:10", value.toString());
+    }
+
+    public void testSqlTimestampType() {
+        DateConverter converter = new DateConverter();
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(mxLocale);
+
+        Object value = converter.convertValue(context.getContextMap(), null, null, null, INPUT_TIME_STAMP_STR, Timestamp.class);
+        assertEquals(RES_TIME_STAMP_STR, value.toString());
+    }
+
+    public void testDateType() {
+        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, DATE_STR, Date.class);
+        assertTrue(value.toString().startsWith(DATE_CONVERTED));
+    }
+
+    public void testTypeConversionExceptionWhenParseError() {
+        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_DATE, Date.class);
+            fail("TypeConversionException expected - Conversion error occurred");
+        } catch (Exception ex) {
+            assertEquals(TypeConversionException.class, ex.getClass());
+            assertEquals(MESSAGE_PARSE_ERROR, ex.getMessage());
+        }
+    }
+
+    public void testTypeConversionExceptionWhenUsingLongConstructor() {
+        DateConverter converter = new DateConverter();
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(mxLocale);
+
+        try {
+            converter.convertValue(context.getContextMap(), null, null, null, INPUT_WHEN_LONG_CONSTRUCTOR_STR, null);
+            fail("TypeConversionException expected - Error using default (long) constructor");
+        } catch (Exception ex) {
+            assertEquals(TypeConversionException.class, ex.getClass());
+            assertEquals(MESSAGE_DEFAULT_CONSTRUCTOR_ERROR, ex.getMessage());
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        //Due to JEP 252: Use CLDR Locale Data by Default
+        DateFormat dFormat = DateFormat.getDateInstance(DateFormat.SHORT, mxLocale);
+        if (dFormat.format(new Date()).contains("-")) {            // Format when Java 9 or greater
+            INPUT_TIME_STAMP_STR = "2020-03-20 00:00:00.000";
+            INPUT_WHEN_LONG_CONSTRUCTOR_STR = "2020-03-20";
+        } else {// Format when Java 8 or lower
+            INPUT_TIME_STAMP_STR = "03/20/2020 00:00:00.000";
+            INPUT_WHEN_LONG_CONSTRUCTOR_STR = "03/31/20";
+        }
+    }
+
 }
diff --git a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverterTest.java b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverterTest.java
index 4209da1..a1fb332 100644
--- a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverterTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverterTest.java
@@ -19,7 +19,10 @@
 package com.opensymphony.xwork2.conversion.impl;
 
 import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.StubTextProvider;
+import com.opensymphony.xwork2.StubValueStack;
 import com.opensymphony.xwork2.XWorkTestCase;
+import com.opensymphony.xwork2.util.ValueStack;
 import org.apache.struts2.conversion.TypeConversionException;
 import com.opensymphony.xwork2.inject.Container;
 import com.opensymphony.xwork2.test.annotations.Person;
@@ -50,51 +53,80 @@
     // object -> return null when empty string is passed
 
     public void testDateConversionWithEmptyValue() {
-        Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, "", Date.class);
+        Object convertedObject = basicConverter.convertValue(new HashMap<>(), null, null, null, "", Date.class);
         // we must not get StrutsException as that will caused a conversion error
         assertNull(convertedObject);
     }
 
-    public void testDateConversionWithInvalidValue() throws Exception {
+    public void testDateConversionWithInvalidValue() {
+        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 {
-            basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, "asdsd", Date.class);
+            basicConverter.convertValue(context.getContextMap(), null, null, null, "asdsd", Date.class);
             fail("StrutsException expected - conversion error occurred");
         } catch (StrutsException e) {
-            // we MUST get this exception as this is a conversion error
+            assertEquals("Could not parse date", e.getMessage());
         }
     }
 
-    public void testDateWithLocalePoland() throws Exception {
+    public void testDateWithLocalePoland() {
+        Map<String, String> map = new HashMap<>();
+        ValueStack stack = new StubValueStack();
+        stack.push(new StubTextProvider(map));
 
         Locale locale = new Locale("pl", "PL");
-        Map<String, Object> context = createContextWithLocale(locale);
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(locale)
+            .withValueStack(stack);
 
         String reference = "2009-01-09";
-        Object convertedObject = basicConverter.convertValue(context, null, null, null, reference, Date.class);
+        Object convertedObject = basicConverter.convertValue(context.getContextMap(), null, null, null, reference, Date.class);
 
         assertNotNull(convertedObject);
 
         compareDates(locale, convertedObject);
     }
 
-    public void testDateWithLocaleFrance() throws Exception {
+    public void testDateWithLocaleFrance() {
+        Map<String, String> map = new HashMap<>();
+        ValueStack stack = new StubValueStack();
+        stack.push(new StubTextProvider(map));
+
         Locale locale = new Locale("fr", "FR");
-        Map<String, Object> context = createContextWithLocale(locale);
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(locale)
+            .withValueStack(stack);
 
         String reference = "09/01/2009";
-        Object convertedObject = basicConverter.convertValue(context, null, null, null, reference, Date.class);
+        Object convertedObject = basicConverter.convertValue(context.getContextMap(), null, null, null, reference, Date.class);
 
         assertNotNull(convertedObject);
 
         compareDates(locale, convertedObject);
     }
 
-    public void testDateWithLocaleUK() throws Exception {
+    public void testDateWithLocaleUK() {
+        Map<String, String> map = new HashMap<>();
+        ValueStack stack = new StubValueStack();
+        stack.push(new StubTextProvider(map));
+
         Locale locale = new Locale("en", "US");
-        Map<String, Object> context = createContextWithLocale(locale);
+
+        ActionContext context = ActionContext.of(new HashMap<>())
+            .withLocale(locale)
+            .withValueStack(stack);
 
         String reference = "01/09/2009";
-        Object convertedObject = basicConverter.convertValue(context, null, null, null, reference, Date.class);
+        Object convertedObject = basicConverter.convertValue(context.getContextMap(), null, null, null, reference, Date.class);
 
         assertNotNull(convertedObject);
 
@@ -118,21 +150,21 @@
         assertEquals(df.format(cal.getTime()), df.format(convertedObject));
     }
 
-    public void testEmptyArrayConversion() throws Exception {
-        Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, new Object[]{}, Object[].class);
+    public void testEmptyArrayConversion() {
+        Object convertedObject = basicConverter.convertValue(new HashMap<>(), null, null, null, new Object[]{}, Object[].class);
         // we must not get StrutsException as that will caused a conversion error
         assertEquals(Object[].class, convertedObject.getClass());
         Object[] obj = (Object[]) convertedObject;
         assertEquals(0, obj.length);
     }
 
-    public void testNullArrayConversion() throws Exception {
-        Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, null, Object[].class);
+    public void testNullArrayConversion() {
+        Object convertedObject = basicConverter.convertValue(new HashMap<>(), null, null, null, null, Object[].class);
         // we must not get StrutsException as that will caused a conversion error
         assertNull(convertedObject);
     }
 
-    public void testXW490ConvertStringToDouble() throws Exception {
+    public void testXW490ConvertStringToDouble() {
         Locale locale = new Locale("DA"); // let's use a not common locale such as Denmark
         Map<String, Object> context = createContextWithLocale(locale);
 
@@ -144,7 +176,7 @@
         assertEquals(123.99d, value, 0.001d);
     }
 
-    public void testXW49ConvertDoubleToString() throws Exception {
+    public void testXW49ConvertDoubleToString() {
         Locale locale = new Locale("DA"); // let's use a not common locale such as Denmark
         Map<String, Object> context = createContextWithLocale(locale);
 
@@ -202,26 +234,26 @@
         assertEquals(1.46464989f, value);
     }
 
-    public void testNegativeFloatValue() throws Exception {
+    public void testNegativeFloatValue() {
         Object convertedObject = basicConverter.convertValue("-94.1231233", Float.class);
         assertTrue(convertedObject instanceof Float);
         assertEquals(-94.1231233f, (Float) convertedObject, 0.0001);
     }
 
-    public void testPositiveFloatValue() throws Exception {
+    public void testPositiveFloatValue() {
         Object convertedObject = basicConverter.convertValue("94.1231233", Float.class);
         assertTrue(convertedObject instanceof Float);
         assertEquals(94.1231233f, (Float) convertedObject, 0.0001);
     }
 
 
-    public void testNegativeDoubleValue() throws Exception {
+    public void testNegativeDoubleValue() {
         Object convertedObject = basicConverter.convertValue("-94.1231233", Double.class);
         assertTrue(convertedObject instanceof Double);
         assertEquals(-94.1231233d, (Double) convertedObject, 0.0001);
     }
 
-    public void testPositiveDoubleValue() throws Exception {
+    public void testPositiveDoubleValue() {
         Object convertedObject = basicConverter.convertValue("94.1231233", Double.class);
         assertTrue(convertedObject instanceof Double);
         assertEquals(94.1231233d, (Double) convertedObject, 0.0001);
@@ -278,7 +310,7 @@
         assertEquals(BigDecimal.valueOf(12345.67890), convertedObject);
     }
 
-    public void testNestedEnumValue() throws Exception {
+    public void testNestedEnumValue() {
         Object convertedObject = basicConverter.convertValue(ParentClass.NestedEnum.TEST.name(), ParentClass.NestedEnum.class);
         assertTrue(convertedObject instanceof ParentClass.NestedEnum);
         assertEquals(ParentClass.NestedEnum.TEST, convertedObject);
@@ -288,13 +320,13 @@
         Map<String, Object> context = new HashMap<>();
         String s = "names";
         Object value = new Person[0];
-        Class toType = String.class;
+        Class<?> toType = String.class;
         basicConverter.convertValue(context, value, null, s, value, toType);
     }
     
     public void testExceptionWhenCantCreateTypeFromValue() {
         try{
-            Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, 4, Date.class);
+            basicConverter.convertValue(new HashMap<>(), null, null, null, 4, Date.class);
             fail(MSG_EXCEPTION_EXPECTED);
         }catch(Exception ex){
             assertEquals(TypeConversionException.class, ex.getClass());
@@ -304,7 +336,7 @@
     
     public void testExceptionInDoConvertToClass() {
         try{
-            Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, "Foo", Class.class);
+            basicConverter.convertValue(new HashMap<>(), null, null, null, "Foo", Class.class);
             fail(MSG_EXCEPTION_EXPECTED);
         }catch(Exception ex){
             assertEquals(TypeConversionException.class, ex.getClass());
@@ -315,7 +347,7 @@
         try{
             Mockito.when(mockedContainer.getInstanceNames(CollectionConverter.class)).thenReturn(null);
             basicConverter.setContainer(mockedContainer);
-            Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, "Foo", ArrayList.class);
+            basicConverter.convertValue(new HashMap<>(), null, null, null, "Foo", ArrayList.class);
             fail(MSG_EXCEPTION_EXPECTED);
         }catch(Exception ex){
             assertEquals(TypeConversionException.class, ex.getClass());
@@ -328,7 +360,7 @@
             int[] arrayInt = new int[1];
             Mockito.when(mockedContainer.getInstanceNames(ArrayConverter.class)).thenReturn(null);
             basicConverter.setContainer(mockedContainer);
-            Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, "Foo", arrayInt.getClass());
+            basicConverter.convertValue(new HashMap<>(), null, null, null, "Foo", arrayInt.getClass());
             fail(MSG_EXCEPTION_EXPECTED);
         }catch(Exception ex){
             assertEquals(TypeConversionException.class, ex.getClass());
@@ -340,7 +372,7 @@
         try{
             Mockito.when(mockedContainer.getInstanceNames(DateConverter.class)).thenReturn(null);
             basicConverter.setContainer(mockedContainer);
-            Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, "Foo", Date.class);
+            basicConverter.convertValue(new HashMap<>(), null, null, null, "Foo", Date.class);
             fail(MSG_EXCEPTION_EXPECTED);
         }catch(Exception ex){
             assertEquals(TypeConversionException.class, ex.getClass());
@@ -352,7 +384,7 @@
         try{
             Mockito.when(mockedContainer.getInstanceNames(NumberConverter.class)).thenReturn(null);
             basicConverter.setContainer(mockedContainer);
-            Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, "Foo", int.class);
+            basicConverter.convertValue(new HashMap<>(), null, null, null, "Foo", int.class);
             fail(MSG_EXCEPTION_EXPECTED);
         }catch(Exception ex){
             assertEquals(TypeConversionException.class, ex.getClass());
@@ -364,7 +396,7 @@
         try{
             Mockito.when(mockedContainer.getInstanceNames(StringConverter.class)).thenReturn(null);
             basicConverter.setContainer(mockedContainer);
-            Object convertedObject = basicConverter.convertValue(new HashMap<String, Object>(), null, null, null, 1, String.class);
+            basicConverter.convertValue(new HashMap<>(), null, null, null, 1, String.class);
             fail(MSG_EXCEPTION_EXPECTED);
         }catch(Exception ex){
             assertEquals(TypeConversionException.class, ex.getClass());
diff --git a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkConverterTest.java b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkConverterTest.java
index 3465737..9ca0e0e 100644
--- a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkConverterTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/XWorkConverterTest.java
@@ -28,6 +28,8 @@
 import com.opensymphony.xwork2.util.FurColor;
 import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
 import ognl.OgnlRuntime;
+import ognl.TypeConverter;
+import org.apache.struts2.components.*;
 
 import java.io.IOException;
 import java.math.BigDecimal;
@@ -41,6 +43,8 @@
 
 import static org.junit.Assert.assertArrayEquals;
 
+import java.util.Date;
+import java.util.Set;
 
 /**
  * @author $Author$
@@ -122,6 +126,29 @@
         assertEquals(date, dateRfc3339DateOnly);
     }
 
+    public void testDateConversionWithDefault() throws ParseException {
+        Map<String, String> lookupMap = new HashMap<>();
+        TextProvider tp = new StubTextProvider(lookupMap);
+        StubValueStack valueStack = new StubValueStack();
+        valueStack.push(tp);
+        context.put(ActionContext.VALUE_STACK, valueStack);
+
+        String dateToFormat = "2017---06--15";
+        Object unparseableDate = converter.convertValue(context, null, null, null, dateToFormat, Date.class);
+        assertEquals(unparseableDate, com.opensymphony.xwork2.conversion.TypeConverter.NO_CONVERSION_POSSIBLE);
+
+        lookupMap.put(org.apache.struts2.components.Date.DATETAG_PROPERTY, "yyyy---MM--dd");
+
+        SimpleDateFormat format = new SimpleDateFormat("yyyy---MM--dd");
+        Date expectedDate = format.parse(dateToFormat);
+        Object parseableDate = converter.convertValue(context, null, null, null, dateToFormat, Date.class);
+        assertEquals(expectedDate, parseableDate);
+
+        Object standardDate = converter.convertValue(context, null, null, null, "2017-06-15", Date.class);
+        assertEquals(expectedDate, standardDate);
+
+    }
+
     public void testFieldErrorMessageAddedForComplexProperty() {
         SimpleAction action = new SimpleAction();
         action.setBean(new TestBean());
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java
index 7641dcc..44a8e6e 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlUtilTest.java
@@ -19,6 +19,8 @@
 package com.opensymphony.xwork2.ognl;
 
 import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.StubTextProvider;
+import com.opensymphony.xwork2.StubValueStack;
 import com.opensymphony.xwork2.XWorkTestCase;
 import com.opensymphony.xwork2.config.ConfigurationException;
 import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
@@ -64,13 +66,7 @@
 
     // Fields for static field access test
     public static final String STATIC_FINAL_PUBLIC_ATTRIBUTE = "Static_Final_Public_Attribute";
-    static final String STATIC_FINAL_PACKAGE_ATTRIBUTE = "Static_Final_Package_Attribute";
-    protected static final String STATIC_FINAL_PROTECTED_ATTRIBUTE = "Static_Final_Protected_Attribute";
-    private static final String STATIC_FINAL_PRIVATE_ATTRIBUTE = "Static_Final_Private_Attribute";
     public static String STATIC_PUBLIC_ATTRIBUTE = "Static_Public_Attribute";
-    static String STATIC_PACKAGE_ATTRIBUTE = "Static_Package_Attribute";
-    protected static String STATIC_PROTECTED_ATTRIBUTE = "Static_Protected_Attribute";
-    private static String STATIC_PRIVATE_ATTRIBUTE = "Static_Private_Attribute";
 
     private OgnlUtil ognlUtil;
 
@@ -97,9 +93,7 @@
                 for (Method method : methods) {
                     String name = method.getName();
 
-                    if (!getter.equals(name) || (method.getParameterTypes().length != 1)) {
-                        continue;
-                    } else {
+                    if (getter.equals(name) && (method.getParameterTypes().length == 1)) {
                         Class<?> clazz = method.getParameterTypes()[0];
 
                         try {
@@ -477,10 +471,17 @@
 
         Map<String, Object> context = ognlUtil.createDefaultContext(foo);
 
+        ValueStack stack = new StubValueStack();
+        stack.push(new StubTextProvider(new HashMap<>()));
+
         Map<String, Object> props = new HashMap<>();
         props.put("birthday", "02/12/1982");
         // US style test
-        context = ActionContext.of(context).withLocale(Locale.US).getContextMap();
+        context = ActionContext.of(context)
+            .withLocale(Locale.US)
+            .withValueStack(stack)
+            .getContextMap();
+
         ognlUtil.setProperties(props, foo, context);
 
         Calendar cal = Calendar.getInstance(Locale.US);
diff --git a/core/src/test/java/com/opensymphony/xwork2/validator/SimpleActionValidationTest.java b/core/src/test/java/com/opensymphony/xwork2/validator/SimpleActionValidationTest.java
index 503d230..b2e1f2a 100644
--- a/core/src/test/java/com/opensymphony/xwork2/validator/SimpleActionValidationTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/validator/SimpleActionValidationTest.java
@@ -21,6 +21,8 @@
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.ActionProxy;
 import com.opensymphony.xwork2.SimpleAction;
+import com.opensymphony.xwork2.StubTextProvider;
+import com.opensymphony.xwork2.StubValueStack;
 import com.opensymphony.xwork2.TextProviderFactory;
 import com.opensymphony.xwork2.XWorkTestCase;
 import com.opensymphony.xwork2.config.providers.MockConfigurationProvider;
@@ -44,7 +46,7 @@
 public class SimpleActionValidationTest extends XWorkTestCase {
 
     public void testAliasValidation() {
-        HashMap<String, Object> params = new HashMap<>();
+        Map<String, Object> params = new HashMap<>();
         params.put("baz", "10");
 
         //valid values
@@ -52,8 +54,10 @@
         params.put("date", "12/23/2002");
         params.put("percentage", "1.23456789");
 
-        HashMap<String, Object> extraContext = new HashMap<>();
-        extraContext.put(ActionContext.PARAMETERS, HttpParameters.create(params).build());
+        Map<String, Object> extraContext = ActionContext.of(new HashMap<>())
+            .withParameters(HttpParameters.create(params).build())
+            .bind()
+            .getContextMap();
 
         try {
             ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ACTION_NAME, null, extraContext);
@@ -62,11 +66,11 @@
             ValidationAware validationAware = (ValidationAware) proxy.getAction();
             assertFalse(validationAware.hasFieldErrors());
 
-            // put in an out-of-range value to see if the old validators still work
-            ActionContext.of(new HashMap<>()).bind();
-
             params.put("bar", "42");
-            extraContext.put(ActionContext.PARAMETERS, HttpParameters.create(params).build());
+            extraContext = ActionContext.of(new HashMap<>())
+                .withParameters(HttpParameters.create(params).build())
+                .bind()
+                .getContextMap();
 
             proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.VALIDATION_ALIAS_NAME, null, extraContext);
             proxy.execute();