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"> </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";
+ }
+
+}