You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2014/03/27 19:42:49 UTC

[1/2] git commit: WW-4146 Caches only valid Ognl expressions to avoid cache attack

Repository: struts
Updated Branches:
  refs/heads/develop 5fb27ba7e -> 9c9d2b5a5


WW-4146 Caches only valid Ognl expressions to avoid cache attack


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/86813c1a
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/86813c1a
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/86813c1a

Branch: refs/heads/develop
Commit: 86813c1a7214bc002a5d7ce9981a9ef333e27142
Parents: 5fb27ba
Author: Lukasz Lenart <lu...@apache.org>
Authored: Thu Mar 27 19:07:11 2014 +0100
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Thu Mar 27 19:07:11 2014 +0100

----------------------------------------------------------------------
 .../com/opensymphony/xwork2/ognl/OgnlUtil.java  | 85 ++++++++++++++------
 .../ognl/accessor/CompoundRootAccessor.java     | 27 ++++---
 2 files changed, 76 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/86813c1a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java
----------------------------------------------------------------------
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java
index 3e622d5..fa907e3 100644
--- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/OgnlUtil.java
@@ -226,12 +226,16 @@ public class OgnlUtil {
         setValue(name, context, root, value, true);
     }
 
-    protected void setValue(String name, Map<String, Object> context, Object root, Object value, boolean evalName) throws OgnlException {
-        Object tree = compile(name, context);
-        if (!evalName && isEvalExpression(tree, context)) {
-            throw new OgnlException("Eval expression cannot be used as parameter name");
-        }
-        Ognl.setValue(tree, context, root, value);
+    protected void setValue(String name, final Map<String, Object> context, final Object root, final Object value, final boolean evalName) throws OgnlException {
+        compileAndExecute(name, context, new OgnlTask<Void>() {
+            public Void execute(Object tree) throws OgnlException {
+                if (!evalName && isEvalExpression(tree, context)) {
+                    throw new OgnlException("Eval expression cannot be used as parameter name");
+                }
+                Ognl.setValue(tree, context, root, value);
+                return null;
+            }
+        });
     }
 
     private boolean isEvalExpression(Object tree, Map<String, Object> context) throws OgnlException {
@@ -247,12 +251,20 @@ public class OgnlUtil {
         return false;
     }
 
-    public Object getValue(String name, Map<String, Object> context, Object root) throws OgnlException {
-        return Ognl.getValue(compile(name, context), context, root);
+    public Object getValue(final String name, final Map<String, Object> context, final Object root) throws OgnlException {
+        return compileAndExecute(name, context, new OgnlTask<Object>() {
+            public Object execute(Object tree) throws OgnlException {
+                return Ognl.getValue(tree, context, root);
+            }
+        });
     }
 
-    public Object getValue(String name, Map<String, Object> context, Object root, Class resultType) throws OgnlException {
-        return Ognl.getValue(compile(name, context), context, root, resultType);
+    public Object getValue(final String name, final Map<String, Object> context, final Object root, final Class resultType) throws OgnlException {
+        return compileAndExecute(name, context, new OgnlTask<Object>() {
+            public Object execute(Object tree) throws OgnlException {
+                return Ognl.getValue(tree, context, root, resultType);
+            }
+        });
     }
 
 
@@ -260,7 +272,7 @@ public class OgnlUtil {
         return compile(expression, null);
     }
 
-    public Object compile(String expression, Map<String, Object> context) throws OgnlException {
+    private <T> Object compileAndExecute(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {
         Object tree;
         if (enableExpressionCache) {
             tree = expressions.get(expression);
@@ -274,7 +286,19 @@ public class OgnlUtil {
             checkEnableEvalExpression(tree, context);
         }
 
-        return tree;
+
+        final T exec = task.execute(tree);
+        if(enableExpressionCache)
+            expressions.putIfAbsent(expression, tree);
+        return exec;
+    }
+
+    public Object compile(String expression, Map<String, Object> context) throws OgnlException {
+        return compileAndExecute(expression,context,new OgnlTask<Object>() {
+            public Object execute(Object tree) throws OgnlException {
+                return tree;
+            }
+        });
     }
     
     private void checkEnableEvalExpression(Object tree, Map<String, Object> context) throws OgnlException {
@@ -295,7 +319,7 @@ public class OgnlUtil {
      * @param inclusions collection of method names to included copying  (can be null)
      *                   note if exclusions AND inclusions are supplied and not null nothing will get copied.
      */
-    public void copy(Object from, Object to, Map<String, Object> context, Collection<String> exclusions, Collection<String> inclusions) {
+    public void copy(final Object from, final Object to, final Map<String, Object> context, Collection<String> exclusions, Collection<String> inclusions) {
         if (from == null || to == null) {
             if (LOG.isWarnEnabled()) {
                 LOG.warn("Attempting to copy from or to a null source. This is illegal and is bein skipped. This may be due to an error in an OGNL expression, action chaining, or some other event.");
@@ -305,9 +329,9 @@ public class OgnlUtil {
         }
 
         TypeConverter conv = getTypeConverterFromContext(context);
-        Map contextFrom = Ognl.createDefaultContext(from);
+        final Map contextFrom = Ognl.createDefaultContext(from);
         Ognl.setTypeConverter(contextFrom, conv);
-        Map contextTo = Ognl.createDefaultContext(to);
+        final Map contextTo = Ognl.createDefaultContext(to);
         Ognl.setTypeConverter(contextTo, conv);
 
         PropertyDescriptor[] fromPds;
@@ -342,9 +366,14 @@ public class OgnlUtil {
                     PropertyDescriptor toPd = toPdHash.get(fromPd.getName());
                     if ((toPd != null) && (toPd.getWriteMethod() != null)) {
                         try {
-                            Object expr = compile(fromPd.getName(), context);
-                            Object value = Ognl.getValue(expr, contextFrom, from);
-                            Ognl.setValue(expr, contextTo, to, value);
+                            compileAndExecute(fromPd.getName(), context, new OgnlTask<Object>() {
+                                public Void execute(Object expr) throws OgnlException {
+                                    Object value = Ognl.getValue(expr, contextFrom, from);
+                                    Ognl.setValue(expr, contextTo, to, value);
+                                    return null;
+                                }
+                            });
+
                         } catch (OgnlException e) {
                             if (LOG.isDebugEnabled()) {
                                 LOG.debug("Got OGNL exception", e);
@@ -409,16 +438,19 @@ public class OgnlUtil {
      * @throws IntrospectionException is thrown if an exception occurs during introspection.
      * @throws OgnlException          is thrown by OGNL if the property value could not be retrieved
      */
-    public Map getBeanMap(Object source) throws IntrospectionException, OgnlException {
-        Map beanMap = new HashMap();
-        Map sourceMap = Ognl.createDefaultContext(source);
+    public Map<String, Object> getBeanMap(final Object source) throws IntrospectionException, OgnlException {
+        Map<String, Object> beanMap = new HashMap<String, Object>();
+        final Map sourceMap = Ognl.createDefaultContext(source);
         PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(source);
         for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
-            String propertyName = propertyDescriptor.getDisplayName();
+            final String propertyName = propertyDescriptor.getDisplayName();
             Method readMethod = propertyDescriptor.getReadMethod();
             if (readMethod != null) {
-                Object expr = compile(propertyName);
-                Object value = Ognl.getValue(expr, sourceMap, source);
+                final Object value = compileAndExecute(propertyName, null, new OgnlTask<Object>() {
+                    public Object execute(Object expr) throws OgnlException {
+                        return Ognl.getValue(expr, sourceMap, source);
+                    }
+                });
                 beanMap.put(propertyName, value);
             } else {
                 beanMap.put(propertyName, "There is no read method for " + propertyName);
@@ -485,4 +517,9 @@ public class OgnlUtil {
         */
         return defaultConverter;
     }
+
+    private interface OgnlTask<T> {
+        T execute(Object tree) throws OgnlException;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/struts/blob/86813c1a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java
----------------------------------------------------------------------
diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java
index df038b0..58942fd 100644
--- a/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java
+++ b/xwork-core/src/main/java/com/opensymphony/xwork2/ognl/accessor/CompoundRootAccessor.java
@@ -17,8 +17,8 @@ package com.opensymphony.xwork2.ognl.accessor;
 
 import com.opensymphony.xwork2.XWorkConstants;
 import com.opensymphony.xwork2.XWorkException;
-import com.opensymphony.xwork2.ognl.OgnlValueStack;
 import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.ognl.OgnlValueStack;
 import com.opensymphony.xwork2.util.CompoundRoot;
 import com.opensymphony.xwork2.util.ValueStack;
 import com.opensymphony.xwork2.util.logging.Logger;
@@ -29,6 +29,8 @@ import java.beans.IntrospectionException;
 import java.beans.PropertyDescriptor;
 import java.util.*;
 
+import static java.lang.String.format;
+import static org.apache.commons.lang3.BooleanUtils.toBoolean;
 
 /**
  * A stack that is able to call methods on objects in the stack.
@@ -55,7 +57,7 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
 
     private final static Logger LOG = LoggerFactory.getLogger(CompoundRootAccessor.class);
     private final static Class[] EMPTY_CLASS_ARRAY = new Class[0];
-    private static Map invalidMethods = new HashMap();
+    private static Map<MethodCall, Boolean> invalidMethods = new HashMap<MethodCall, Boolean>();
 
     static boolean devMode = false;
 
@@ -79,7 +81,8 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
 
                     return;
                 } else if (o instanceof Map) {
-                    Map<Object, Object> map = (Map) o;
+                    @SuppressWarnings("unchecked")
+                    Map<Object, Object> map = (Map<Object, Object>) o;
                     try {
                         map.put(name, value);
                         return;
@@ -98,14 +101,14 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
             }
         }
 
-        Boolean reportError = (Boolean) context.get(ValueStack.REPORT_ERRORS_ON_NO_PROP);
-
-        final String msg = "No object in the CompoundRoot has a publicly accessible property named '" + name + "' (no setter could be found).";
+        boolean reportError = toBoolean((Boolean) context.get(ValueStack.REPORT_ERRORS_ON_NO_PROP));
 
-        if ((reportError != null) && (reportError.booleanValue())) {
-            throw new XWorkException(msg);
-        } else {
-            if (devMode) {
+        if (reportError || devMode) {
+            final String msg = format("No object in the CompoundRoot has a publicly accessible property named '%s' " +
+                    "(no setter could be found).", name);
+            if (reportError) {
+                throw new XWorkException(msg);
+            } else {
                 LOG.warn(msg);
             }
         }
@@ -118,7 +121,7 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
         if (name instanceof Integer) {
             Integer index = (Integer) name;
 
-            return root.cutStack(index.intValue());
+            return root.cutStack(index);
         } else if (name instanceof String) {
             if ("top".equals(name)) {
                 if (root.size() > 0) {
@@ -234,7 +237,7 @@ public class CompoundRootAccessor implements PropertyAccessor, MethodAccessor, C
 
             if ((argTypes == null) || !invalidMethods.containsKey(mc)) {
                 try {
-                    Object value = OgnlRuntime.callMethod((OgnlContext) context, o, name, name, objects);
+                    Object value = OgnlRuntime.callMethod((OgnlContext) context, o, name, objects);
 
                     if (value != null) {
                         return value;


[2/2] git commit: WW-3493 Adds new datetextfield tag

Posted by lu...@apache.org.
WW-3493 Adds new datetextfield tag


Project: http://git-wip-us.apache.org/repos/asf/struts/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/9c9d2b5a
Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/9c9d2b5a
Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/9c9d2b5a

Branch: refs/heads/develop
Commit: 9c9d2b5a58f13b0b8704a30e3f53078154a9a6b8
Parents: 86813c1
Author: Lukasz Lenart <lu...@apache.org>
Authored: Thu Mar 27 19:42:35 2014 +0100
Committer: Lukasz Lenart <lu...@apache.org>
Committed: Thu Mar 27 19:42:35 2014 +0100

----------------------------------------------------------------------
 .../struts2/components/DateTextField.java       |  51 +++
 .../interceptor/DateTextFieldInterceptor.java   | 128 +++++++
 .../struts2/views/jsp/ui/DateTextFieldTag.java  |  35 ++
 core/src/main/resources/struts-default.xml      |   4 +
 core/src/site/resources/tags/datetextfield.html | 376 +++++++++++++++++++
 .../DateTextFieldInterceptorTest.java           |  58 +++
 .../views/java/simple/DateTextFieldHandler.java |  91 +++++
 .../struts2/views/java/simple/SimpleTheme.java  |   1 +
 .../views/java/simple/DateTextFieldTest.java    |  51 +++
 9 files changed, 795 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/core/src/main/java/org/apache/struts2/components/DateTextField.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/struts2/components/DateTextField.java b/core/src/main/java/org/apache/struts2/components/DateTextField.java
new file mode 100644
index 0000000..0fd3614
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/components/DateTextField.java
@@ -0,0 +1,51 @@
+package org.apache.struts2.components;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.struts2.views.annotations.StrutsTag;
+import org.apache.struts2.views.annotations.StrutsTagAttribute;
+
+import com.opensymphony.xwork2.util.ValueStack;
+
+@StrutsTag(
+    name="datetextfield",
+    tldTagClass="org.apache.struts2.views.jsp.ui.DateTextFieldTag",
+    description="Render an HTML input fields with the date time",
+    allowDynamicAttributes=true)
+public class DateTextField extends UIBean {
+    /**
+     * The name of the default template for the DateTextFieldTag
+     */
+    final public static String TEMPLATE = "datetextfield";
+    
+    protected String format;
+
+    public DateTextField(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
+        super(stack, request, response);
+    }
+
+    protected String getDefaultTemplate() {
+        return TEMPLATE;
+    }
+
+    protected void evaluateExtraParams() {
+        super.evaluateExtraParams();
+
+        if (format != null) {
+            addParameter("format", findValue(format, String.class));
+        }
+    }
+
+    @StrutsTagAttribute(description="Date format attribute", required=true, type="String")
+    public void setFormat(String format) {
+        this.format = format;
+    }
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected Class getValueClassType() {
+		return null;
+	}
+    
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/core/src/main/java/org/apache/struts2/interceptor/DateTextFieldInterceptor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/struts2/interceptor/DateTextFieldInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/DateTextFieldInterceptor.java
new file mode 100644
index 0000000..f5bda69
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/DateTextFieldInterceptor.java
@@ -0,0 +1,128 @@
+package org.apache.struts2.interceptor;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+import com.opensymphony.xwork2.util.logging.Logger;
+import com.opensymphony.xwork2.util.logging.LoggerFactory;
+
+public class DateTextFieldInterceptor implements Interceptor {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DateTextFieldInterceptor.class);
+
+    public static enum DateWord {
+
+		S("millisecond", 3, "SSS"),
+		s("second", 2, "ss"),
+		m("minute", 2, "mm"),
+		H("hour", 2, "HH"),
+		d("day", 2, "dd"),
+		M("month", 2, "MM"),
+		y("year", 4, "yyyy");
+
+		private String description;
+		private Integer length;
+		private String dateType;
+		
+		private DateWord(String n, Integer l, String t) {
+			description = n;
+			length = l;
+			dateType = t;
+		}
+
+        public String getDescription() {
+            return description;
+        }
+
+        public Integer getLength() {
+            return length;
+        }
+
+        public String getDateType() {
+            return dateType;
+        }
+
+        public static DateWord get(Character c) {
+            return valueOf(DateWord.class, c.toString());
+        }
+
+        public static DateWord[] getAll() {
+            return values();
+        }
+    }
+    
+    public void destroy() {
+    }
+
+    public void init() {
+    }
+
+    public String intercept(ActionInvocation ai) throws Exception {
+        Map<String, Object> parameters = ai.getInvocationContext().getParameters();
+        Set<Entry<String, Object>> entries = parameters.entrySet();
+        Map<String, Map<String, String>> dates = new HashMap<String, Map<String,String>>();
+        
+        DateWord[] dateWords = DateWord.getAll();
+
+        // Get all the values of date type
+        for (Iterator<Entry<String, Object>> iterator = entries.iterator(); iterator.hasNext();) {
+            Entry<String, ?> entry = iterator.next();
+            String key = entry.getKey();
+
+            for (DateWord dateWord : dateWords) {
+            	String dateKey = "__" + dateWord.getDescription() + "_";
+            	if (key.startsWith(dateKey)) {
+                    String name = key.substring(dateKey.length());
+
+                    if (entry.getValue() instanceof String[]) {
+                    	String[] values = (String[])entry.getValue();
+                    	if (values.length > 0 && !"".equals(values[0])) {
+                    		iterator.remove();
+                    		Map<String, String> map = dates.get(name);
+                    		if (map == null) {
+                    			map = new HashMap<String, String>();
+                            	dates.put(name, map);
+                    		}
+                            map.put(dateWord.getDateType(), values[0]);
+                    	}
+                    }
+                    break;
+                }
+            }
+        }
+
+        // Create all the date objects
+        Map<String, Date> newParams = new HashMap<String, Date>();
+        Set<Entry<String, Map<String, String>>> dateEntries = dates.entrySet();
+        for (Entry<String, Map<String, String>> dateEntry : dateEntries) {
+        	Set<Entry<String, String>> dateFormatEntries = dateEntry.getValue().entrySet();
+        	String dateFormat = "";
+        	String dateValue = "";
+        	for (Entry<String, String> dateFormatEntry : dateFormatEntries) {
+        		dateFormat += dateFormatEntry.getKey() + "__";
+        		dateValue += dateFormatEntry.getValue() + "__";
+        	}
+            try {
+            	SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
+            	formatter.setLenient(false);
+                Date value = formatter.parse(dateValue);
+                newParams.put(dateEntry.getKey(), value);
+            } catch (ParseException e) {
+            	LOG.warn("Cannot parse the parameter '" + dateEntry.getKey() 
+            			+ "' with format '" + dateFormat + "' and with value '" + dateValue + "'");
+            }
+        }
+        parameters.putAll(newParams);
+
+        return ai.invoke();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/core/src/main/java/org/apache/struts2/views/jsp/ui/DateTextFieldTag.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/struts2/views/jsp/ui/DateTextFieldTag.java b/core/src/main/java/org/apache/struts2/views/jsp/ui/DateTextFieldTag.java
new file mode 100644
index 0000000..3b3a673
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/views/jsp/ui/DateTextFieldTag.java
@@ -0,0 +1,35 @@
+package org.apache.struts2.views.jsp.ui;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.struts2.components.Component;
+import org.apache.struts2.components.DateTextField;
+
+import com.opensymphony.xwork2.util.ValueStack;
+
+/**
+ * @see DateTextField
+ */
+public class DateTextFieldTag extends AbstractUITag {
+
+    private static final long serialVersionUID = 5811285953670562288L;
+
+    protected String format;
+    
+    public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
+        return new DateTextField(stack, req, res);
+    }
+
+    protected void populateParams() {
+        super.populateParams();
+
+        DateTextField textField = ((DateTextField) component);
+        textField.setFormat(format);
+    }
+
+	public void setFormat(String format) {
+		this.format = format;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/core/src/main/resources/struts-default.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml
index a99a945..5c446b1 100644
--- a/core/src/main/resources/struts-default.xml
+++ b/core/src/main/resources/struts-default.xml
@@ -186,6 +186,7 @@
             <interceptor name="workflow" class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/>
             <interceptor name="store" class="org.apache.struts2.interceptor.MessageStoreInterceptor" />
             <interceptor name="checkbox" class="org.apache.struts2.interceptor.CheckboxInterceptor" />
+            <interceptor name="datetime" class="org.apache.struts2.interceptor.DateTextFieldInterceptor" />
             <interceptor name="profiling" class="org.apache.struts2.interceptor.ProfilingActivationInterceptor" />
             <interceptor name="roles" class="org.apache.struts2.interceptor.RolesInterceptor" />
             <interceptor name="annotationWorkflow" class="com.opensymphony.xwork2.interceptor.annotations.AnnotationWorkflowInterceptor" />
@@ -198,6 +199,7 @@
                 <interceptor-ref name="servletConfig"/>
                 <interceptor-ref name="prepare"/>
                 <interceptor-ref name="checkbox"/>
+                <interceptor-ref name="datetime"/>
                 <interceptor-ref name="multiselect"/>
                 <interceptor-ref name="actionMappingParams"/>
                 <interceptor-ref name="params">
@@ -255,6 +257,7 @@
                 <interceptor-ref name="alias"/>
                 <interceptor-ref name="i18n"/>
                 <interceptor-ref name="checkbox"/>
+                <interceptor-ref name="datetime"/>
                 <interceptor-ref name="multiselect"/>
                 <interceptor-ref name="params">
                     <param name="excludeParams">^class\..*,^dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,^parameters\..*,^action:.*,^method:.*</param>
@@ -300,6 +303,7 @@
                 <interceptor-ref name="modelDriven"/>
                 <interceptor-ref name="fileUpload"/>
                 <interceptor-ref name="checkbox"/>
+                <interceptor-ref name="datetime"/>
                 <interceptor-ref name="multiselect"/>
                 <interceptor-ref name="staticParams"/>
                 <interceptor-ref name="actionMappingParams"/>

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/core/src/site/resources/tags/datetextfield.html
----------------------------------------------------------------------
diff --git a/core/src/site/resources/tags/datetextfield.html b/core/src/site/resources/tags/datetextfield.html
new file mode 100644
index 0000000..2bfa1fe
--- /dev/null
+++ b/core/src/site/resources/tags/datetextfield.html
@@ -0,0 +1,376 @@
+<!--
+This file is generated during the build by processing Component class annotations.
+Please do not edit it directly.
+-->
+<html>
+    <head>
+		<title>datetextfield</title>
+	</head>
+
+	<body>
+		<h1>Tag Name: datetextfield</h1>
+		<h2>Description</h2>
+		<p>
+		<!-- START SNIPPET: tagdescription -->
+		Render an HTML input fields with the date time
+		<!-- END SNIPPET: tagdescription -->
+		</p>
+
+		<h2>Attributes</h2>
+		<!-- START SNIPPET: tagattributes -->
+		<table width="100%">
+			<tr>
+				<td colspan="6"><h4>Dynamic Attributes Allowed:</h4> true</td>
+			</tr>
+			<tr>
+				<td colspan="6">&nbsp;</td>
+			</tr>
+			<tr>
+				<th align="left" valign="top"><h4>Name</h4></th>
+				<th align="left" valign="top"><h4>Required</h4></th>
+				<th align="left" valign="top"><h4>Default</h4></th>
+				<th align="left" valign="top"><h4>Evaluated</h4></th>
+				<th align="left" valign="top"><h4>Type</h4></th>
+				<th align="left" valign="top"><h4>Description</h4></th>
+			</tr>
+				<tr>
+					<td align="left" valign="top">accesskey</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html accesskey attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">cssClass</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The css class to use for element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">cssErrorClass</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The css error class to use for element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">cssErrorStyle</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The css error style definitions for element to use</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">cssStyle</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The css style definitions for element to use</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">disabled</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html disabled attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">errorPosition</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Define error position of form element (top|bottom)</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">format</td>
+					<td align="left" valign="top"><strong>true</strong></td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Date format attribute</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">id</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">HTML id attribute</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">javascriptTooltip</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">Boolean</td>
+					<td align="left" valign="top">Use JavaScript to generate tooltips</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">key</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the key (name, value, label) for this particular component</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">label</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Label expression used for rendering an element specific label</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">labelSeparator</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">:</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">String that will be appended to the label</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">labelposition</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Define label position of form element (top/left)</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">name</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The name to set for element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onblur</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top"> Set the html onblur attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onchange</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onchange attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onclick</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onclick attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">ondblclick</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html ondblclick attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onfocus</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onfocus attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onkeydown</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onkeydown attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onkeypress</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onkeypress attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onkeyup</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onkeyup attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onmousedown</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onmousedown attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onmousemove</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onmousemove attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onmouseout</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onmouseout attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onmouseover</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onmouseover attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onmouseup</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onmouseup attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">onselect</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html onselect attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">requiredLabel</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">Boolean</td>
+					<td align="left" valign="top">If set to true, the rendered element will indicate that input is required</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">requiredPosition</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Define required position of required form element (left|right)</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">tabindex</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html tabindex attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">template</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The template (other than default) to use for rendering the element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">templateDir</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The template directory.</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">theme</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">The theme (other than default) to use for rendering the element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">title</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the html title attribute on rendered html element</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">tooltip</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Set the tooltip of this particular component</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">tooltipConfig</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Deprecated. Use individual tooltip configuration attributes instead.</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">tooltipCssClass</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">StrutsTTClassic</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">CSS class applied to JavaScrip tooltips</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">tooltipDelay</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">Classic</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Delay in milliseconds, before showing JavaScript tooltips </td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">tooltipIconPath</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Icon path used for image that will have the tooltip</td>
+				</tr>
+				<tr>
+					<td align="left" valign="top">value</td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top"></td>
+					<td align="left" valign="top">false</td>
+					<td align="left" valign="top">String</td>
+					<td align="left" valign="top">Preset the value of input element.</td>
+				</tr>
+		</table>
+		<!-- END SNIPPET: tagattributes -->
+	</body>
+</html>
+

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/core/src/test/java/org/apache/struts2/interceptor/DateTextFieldInterceptorTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/struts2/interceptor/DateTextFieldInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/DateTextFieldInterceptorTest.java
new file mode 100644
index 0000000..e146239
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/interceptor/DateTextFieldInterceptorTest.java
@@ -0,0 +1,58 @@
+package org.apache.struts2.interceptor;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.struts2.StrutsInternalTestCase;
+
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.mock.MockActionInvocation;
+
+/**
+ * Unit test for DateTextFieldInterceptor. 
+ */
+public class DateTextFieldInterceptorTest extends StrutsInternalTestCase {
+
+    private DateTextFieldInterceptor interceptor;
+    private MockActionInvocation ai;
+    private Map<String, Object> param;
+    
+    protected void setUp() throws Exception {
+    	super.setUp();
+    	param = new HashMap<String, Object>();
+    	
+    	interceptor = new DateTextFieldInterceptor();
+    	ai = new MockActionInvocation();
+    	ai.setInvocationContext(ActionContext.getContext());
+    	ActionContext.getContext().setParameters(param);
+    }
+	
+	public void testNoParam() throws Exception {
+		interceptor.init();
+		interceptor.intercept(ai);
+		interceptor.destroy();
+
+		assertEquals(0, param.size());
+	}
+
+	public void testOneDateTextField() throws Exception {
+		param.put("__year_name", new String[]{"2000"});
+		param.put("__month_name", new String[]{"06"});
+		param.put("__day_name", new String[]{"15"});
+
+		interceptor.init();
+		interceptor.intercept(ai);
+		interceptor.destroy();
+		
+		assertFalse(param.containsKey("__year_name"));
+		assertFalse(param.containsKey("__month_name"));
+		assertFalse(param.containsKey("__day_name"));
+		assertTrue(param.containsKey("name"));
+		assertEquals(1, param.size());
+		Date date = new SimpleDateFormat("yyyy-MM-dd").parse("2000-06-15"); 
+		assertEquals(date, param.get("name"));
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java
----------------------------------------------------------------------
diff --git a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java
new file mode 100644
index 0000000..80aed25
--- /dev/null
+++ b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/DateTextFieldHandler.java
@@ -0,0 +1,91 @@
+package org.apache.struts2.views.java.simple;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.struts2.interceptor.DateTextFieldInterceptor.DateWord;
+import org.apache.struts2.views.java.Attributes;
+import org.apache.struts2.views.java.TagGenerator;
+
+
+public class DateTextFieldHandler extends AbstractTagHandler implements TagGenerator {
+
+    @SuppressWarnings("unchecked")
+	public void generate() throws IOException {
+        Map<String, Object> params = context.getParameters();
+        Attributes attr = null;
+
+        // Get format
+        String format = (String)params.get("format");
+        String id = (String)params.get("id");
+        String name = (String)params.get("name");
+        if (id == null) {
+        	id = name;
+        }
+        Date date = (Date)params.get("nameValue");
+        
+        if (format != null) {
+        	// Verify if it's correct
+            new SimpleDateFormat(format);
+            
+            attr = new Attributes();
+            attr.addIfExists("id", id);
+            super.start("div", attr);
+            
+            Character antC = null;
+            for (Character c : format.toCharArray()) {
+            	
+            	try {
+            		DateWord dateWord = DateWord.get(c);
+            		if (!c.equals(antC)) {
+            			
+            			String cssClass = "date_" + dateWord.getDescription();
+            			if (params.get("cssClass") != null) {
+            				cssClass += " " + params.get("cssClass");
+            			}
+            			
+            			attr = new Attributes();
+            	        attr.add("type", "text")
+            	                .addIfExists("class", cssClass)
+            	                .addIfExists("size", dateWord.getLength())
+            	                .addIfExists("maxlength", dateWord.getLength())
+            	                .addIfTrue("disabled", params.get("disabled"))
+            	                .addIfTrue("readonly", params.get("readonly"))
+            	                .addIfExists("tabindex", params.get("tabindex"))
+            	                .addIfExists("style", params.get("cssStyle"))
+            	                .addIfExists("title", params.get("title"));
+            	        
+            	        if (id != null && !"".equals(id)) {
+                	        attr.addDefaultToEmpty("id", "__" + dateWord.getDescription() + "_" + id);
+            	        }
+            	        if (name != null && !"".equals(id)) {
+                	        attr.addDefaultToEmpty("name", "__" + dateWord.getDescription() + "_" + name);
+            	        } else {
+                	        attr.addDefaultToEmpty("name", dateWord.getDescription());
+            	        }
+            	        if (date != null) {
+            	        	SimpleDateFormat formatter = new SimpleDateFormat(dateWord.getDateType());
+            	        	attr.addIfExists("value", formatter.format(date), false);
+            	        }
+            	        
+            	        super.start("input", attr);
+            	        super.end("input");
+            	        
+            		}
+            	} catch (IllegalArgumentException e) {
+            		super.characters(c.toString());	
+            	}
+            	antC = c;
+            
+            }
+            super.end("div");
+        }
+        
+    }
+    
+    
+    
+}
+

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/SimpleTheme.java
----------------------------------------------------------------------
diff --git a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/SimpleTheme.java b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/SimpleTheme.java
index 363717b..4c9ba29 100644
--- a/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/SimpleTheme.java
+++ b/plugins/javatemplates/src/main/java/org/apache/struts2/views/java/simple/SimpleTheme.java
@@ -36,6 +36,7 @@ public class SimpleTheme extends DefaultTheme {
             {
                 put("text", new FactoryList(TextFieldHandler.class, ScriptingEventsHandler.class, CommonAttributesHandler.class, DynamicAttributesHandler.class));
                 put("textfield", new FactoryList(TextFieldHandler.class, ScriptingEventsHandler.class, CommonAttributesHandler.class, DynamicAttributesHandler.class));
+                put("datetextfield", new FactoryList(DateTextFieldHandler.class, ScriptingEventsHandler.class, CommonAttributesHandler.class));
                 put("select", new FactoryList(SelectHandler.class, ScriptingEventsHandler.class, CommonAttributesHandler.class, DynamicAttributesHandler.class));
                 put("form", new FactoryList(FormHandler.class, ScriptingEventsHandler.class, CommonAttributesHandler.class, DynamicAttributesHandler.class));
                 put("form-close", new FactoryList(FormHandler.CloseHandler.class));

http://git-wip-us.apache.org/repos/asf/struts/blob/9c9d2b5a/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java
----------------------------------------------------------------------
diff --git a/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java b/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java
new file mode 100644
index 0000000..7c03a65
--- /dev/null
+++ b/plugins/javatemplates/src/test/java/org/apache/struts2/views/java/simple/DateTextFieldTest.java
@@ -0,0 +1,51 @@
+package org.apache.struts2.views.java.simple;
+
+import org.apache.struts2.components.DateTextField;
+import org.apache.struts2.components.UIBean;
+
+public class DateTextFieldTest extends AbstractCommonAttributesTest {
+
+    private DateTextField tag;
+
+    public void testRenderDateTextField() {
+    	tag.setId("id");
+        tag.setName("name");
+        tag.setFormat("yyyy-MM-dd");
+
+        tag.evaluateParams();
+        map.putAll(tag.getParameters());
+        theme.renderTag(getTagName(), context);
+        String output = writer.getBuffer().toString();
+        String expected = s("<div id='id'>" +
+        		"<input type='text' class='date_year' size='4' maxlength='4' id='__year_id' name='__year_name'></input>" +
+        		"-<input type='text' class='date_month' size='2' maxlength='2' id='__month_id' name='__month_name'></input>" +
+        		"-<input type='text' class='date_day' size='2' maxlength='2' id='__day_id' name='__day_name'></input></div>");
+        assertEquals(expected, output);
+    }
+    
+    @Override
+    public void testRenderTextFieldScriptingAttrs() throws Exception { }
+    
+    @Override
+    public void testRenderTextFieldCommonAttrs() throws Exception { }
+
+    @Override
+    public void testRenderTextFieldDynamicAttrs() throws Exception { }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        this.tag = new DateTextField(stack, request, response);
+    }
+
+    @Override
+    protected UIBean getUIBean() {
+        return tag;
+    }
+
+    @Override
+    protected String getTagName() {
+        return "datetextfield";
+    }
+
+}