This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-5016-uses-proper-format in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/WW-5016-uses-proper-format by this push: new e3dff76 WW-5016 Introduces different format adapters to allow use different APIs e3dff76 is described below commit e3dff7691e72a30cf6ebcad1bbace48e56f56380 Author: Lukasz Lenart <lukaszlen...@apache.org> AuthorDate: Sun Feb 20 13:29:46 2022 +0100 WW-5016 Introduces different format adapters to allow use different APIs --- .../StrutsDefaultConfigurationProvider.java | 6 ++ .../java/org/apache/struts2/StrutsConstants.java | 3 + .../java/org/apache/struts2/components/Date.java | 78 ++++++++++--------- .../struts2/components/date/DateFormatter.java | 33 ++++++++ .../components/date/DateTimeFormatterAdapter.java | 47 +++++++++++ .../components/date/SimpleDateFormatAdapter.java | 48 ++++++++++++ .../config/StrutsBeanSelectionProvider.java | 5 +- .../org/apache/struts2/default.properties | 6 ++ core/src/main/resources/struts-default.xml | 5 +- .../org/apache/struts2/components/DateTest.java | 90 ++++++++++++++++++++++ 10 files changed, 280 insertions(+), 41 deletions(-) diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java index 400674d..a5dcf99 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java @@ -56,6 +56,9 @@ import com.opensymphony.xwork2.conversion.impl.DateConverter; import com.opensymphony.xwork2.conversion.impl.DefaultConversionAnnotationProcessor; import com.opensymphony.xwork2.conversion.impl.DefaultConversionFileProcessor; import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker; +import org.apache.struts2.components.date.DateFormatter; +import org.apache.struts2.components.date.DateTimeFormatterAdapter; +import org.apache.struts2.components.date.SimpleDateFormatAdapter; import org.apache.struts2.conversion.StrutsConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer; import org.apache.struts2.conversion.StrutsTypeConverterCreator; @@ -218,6 +221,9 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider , Scope.SINGLETON) .factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON) + + .factory(DateFormatter.class, "simpleDateFormat", SimpleDateFormatAdapter.class, Scope.SINGLETON) + .factory(DateFormatter.class, "dateTimeFormatter", DateTimeFormatterAdapter.class, Scope.SINGLETON) ; props.setProperty(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, Boolean.FALSE.toString()); diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index 789b7c3..64ac93b 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -18,6 +18,7 @@ */ package org.apache.struts2; +import org.apache.struts2.components.date.DateFormatter; import org.apache.struts2.dispatcher.mapper.CompositeActionMapper; /** @@ -384,4 +385,6 @@ public final class StrutsConstants { public static final String STRUTS_CHAINING_COPY_MESSAGES = "struts.chaining.copyMessages"; public static final String STRUTS_OBJECT_FACTORY_CLASSLOADER = "struts.objectFactory.classloader"; + /** See {@link org.apache.struts2.components.Date#setDateFormatter(DateFormatter)} */ + public static final String STRUTS_DATE_FORMATTER = "struts.date.formatter"; } diff --git a/core/src/main/java/org/apache/struts2/components/Date.java b/core/src/main/java/org/apache/struts2/components/Date.java index ec9bb19..0560057 100644 --- a/core/src/main/java/org/apache/struts2/components/Date.java +++ b/core/src/main/java/org/apache/struts2/components/Date.java @@ -18,11 +18,13 @@ */ package org.apache.struts2.components; -import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.TextProvider; +import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.ValueStack; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.struts2.StrutsConstants; +import org.apache.struts2.components.date.DateFormatter; import org.apache.struts2.views.annotations.StrutsTag; import org.apache.struts2.views.annotations.StrutsTagAttribute; @@ -33,8 +35,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.Calendar; import java.util.List; @@ -157,6 +157,7 @@ import java.util.List; public class Date extends ContextBean { private static final Logger LOG = LogManager.getLogger(Date.class); + /** * Property name to fall back when no format is specified */ @@ -208,17 +209,18 @@ public class Date extends ContextBean { private String timezone; + private DateFormatter dateFormatter; + public Date(ValueStack stack) { super(stack); } - private TextProvider findProviderInStack() { - for (Object o : getStack().getRoot()) { - if (o instanceof TextProvider) { - return (TextProvider) o; - } - } - return null; + /** + * An instance of {@link DateFormatter} + */ + @Inject + public void setDateFormatter(DateFormatter dateFormatter) { + this.dateFormatter = dateFormatter; } /** @@ -286,6 +288,8 @@ public class Date extends ContextBean { @Override public boolean end(Writer writer, String body) { + TextProvider textProvider = findProviderInStack(); + ZonedDateTime date = null; final ZoneId tz = getTimeZone(); // find the name on the valueStack @@ -304,10 +308,9 @@ public class Date extends ContextBean { date = ((Instant) dateObject).atZone(tz); } else { if (devMode) { - TextProvider tp = findProviderInStack(); String developerNotification = ""; - if (tp != null) { - developerNotification = findProviderInStack().getText( + if (textProvider != null) { + developerNotification = textProvider.getText( "devmode.notification", "Developer Notification:\n{0}", new String[]{ @@ -329,33 +332,11 @@ public class Date extends ContextBean { } String msg; if (date != null) { - TextProvider tp = findProviderInStack(); - if (tp != null) { + if (textProvider != null) { if (nice) { - msg = formatTime(tp, date); + msg = formatTime(textProvider, date); } else { - DateTimeFormatter dtf; - if (format == null) { - String globalFormat = null; - - // if the format is not specified, fall back using the - // defined property DATETAG_PROPERTY - globalFormat = tp.getText(DATETAG_PROPERTY); - - // if tp.getText can not find the property then the - // returned string is the same as input = - // DATETAG_PROPERTY - if (globalFormat != null - && !DATETAG_PROPERTY.equals(globalFormat)) { - dtf = DateTimeFormatter.ofPattern(globalFormat, ActionContext.getContext().getLocale()); - } else { - dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) - .withLocale(ActionContext.getContext().getLocale()); - } - } else { - dtf = DateTimeFormatter.ofPattern(format, ActionContext.getContext().getLocale()); - } - msg = dtf.format(date); + msg = formatDate(textProvider, date); } if (msg != null) { try { @@ -373,6 +354,18 @@ public class Date extends ContextBean { return super.end(writer, ""); } + private String formatDate(TextProvider textProvider, ZonedDateTime date) { + // if the format is not specified, fall back using the defined property DATETAG_PROPERTY + String globalFormat = textProvider.getText(Date.DATETAG_PROPERTY); + if (DATETAG_PROPERTY.equals(globalFormat)) { + // if tp.getText can not find the property then the + // returned string is the same as input = DATETAG_PROPERTY + globalFormat = null; + } + + return dateFormatter.format(date, format, globalFormat); + } + private ZoneId getTimeZone() { ZoneId tz = ZoneId.systemDefault(); if (timezone != null) { @@ -386,6 +379,15 @@ public class Date extends ContextBean { return tz; } + private TextProvider findProviderInStack() { + for (Object o : getStack().getRoot()) { + if (o instanceof TextProvider) { + return (TextProvider) o; + } + } + return null; + } + @StrutsTagAttribute(description = "Date or DateTime format pattern") public void setFormat(String format) { this.format = format; diff --git a/core/src/main/java/org/apache/struts2/components/date/DateFormatter.java b/core/src/main/java/org/apache/struts2/components/date/DateFormatter.java new file mode 100644 index 0000000..7ad276a --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/date/DateFormatter.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.components.date; + +import java.time.temporal.TemporalAccessor; + +/** + * Allows defines a wrapper around different formatting APIs, like old SimpleDateFormat + * and new DateTimeFormatter introduced in Java 8 Date/Time API + * + * New instance will be injected using {@link org.apache.struts2.StrutsConstants#STRUTS_DATE_FORMATTER} + */ +public interface DateFormatter { + + String format(TemporalAccessor temporal, String format, String defaultFormat); + +} diff --git a/core/src/main/java/org/apache/struts2/components/date/DateTimeFormatterAdapter.java b/core/src/main/java/org/apache/struts2/components/date/DateTimeFormatterAdapter.java new file mode 100644 index 0000000..64bb4b1 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/date/DateTimeFormatterAdapter.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.components.date; + +import com.opensymphony.xwork2.ActionContext; + +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.time.temporal.TemporalAccessor; +import java.util.Locale; + +public class DateTimeFormatterAdapter implements DateFormatter { + + @Override + public String format(TemporalAccessor temporal, String format, String defaultFormat) { + DateTimeFormatter dtf; + Locale locale = ActionContext.getContext().getLocale(); + if (format == null) { + if (defaultFormat != null) { + dtf = DateTimeFormatter.ofPattern(defaultFormat, locale); + } else { + dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) + .withLocale(locale); + } + } else { + dtf = DateTimeFormatter.ofPattern(format, locale); + } + return dtf.format(temporal); + } + +} diff --git a/core/src/main/java/org/apache/struts2/components/date/SimpleDateFormatAdapter.java b/core/src/main/java/org/apache/struts2/components/date/SimpleDateFormatAdapter.java new file mode 100644 index 0000000..e9f29f0 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/date/SimpleDateFormatAdapter.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.components.date; + +import com.opensymphony.xwork2.ActionContext; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.temporal.TemporalAccessor; +import java.util.Date; +import java.util.Locale; + +public class SimpleDateFormatAdapter implements DateFormatter { + + @Override + public String format(TemporalAccessor temporal, String format, String defaultFormat) { + DateFormat df; + Locale locale = ActionContext.getContext().getLocale(); + if (format == null) { + if (defaultFormat != null) { + df = new SimpleDateFormat(defaultFormat, locale); + } else { + df = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM, locale); + } + } else { + df = new SimpleDateFormat(format, locale); + } + return df.format(new Date(Instant.from(temporal).toEpochMilli())); + } + +} diff --git a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java index ade6aa0..69aa925 100644 --- a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java +++ b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java @@ -54,13 +54,12 @@ import com.opensymphony.xwork2.util.PatternMatcher; import com.opensymphony.xwork2.util.TextParser; import com.opensymphony.xwork2.util.ValueStackFactory; import com.opensymphony.xwork2.util.location.LocatableProperties; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; import com.opensymphony.xwork2.util.reflection.ReflectionContextFactory; import com.opensymphony.xwork2.util.reflection.ReflectionProvider; import com.opensymphony.xwork2.validator.ActionValidatorManager; import org.apache.struts2.StrutsConstants; import org.apache.struts2.components.UrlRenderer; +import org.apache.struts2.components.date.DateFormatter; import org.apache.struts2.dispatcher.DispatcherErrorHandler; import org.apache.struts2.dispatcher.StaticContentLoader; import org.apache.struts2.dispatcher.mapper.ActionMapper; @@ -422,6 +421,8 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider { alias(NotExcludedAcceptedPatternsChecker.class, StrutsConstants.STRUTS_NOT_EXCLUDED_ACCEPTED_PATTERNS_CHECKER , builder, props, Scope.SINGLETON); + alias(DateFormatter.class, StrutsConstants.STRUTS_DATE_FORMATTER, builder, props, Scope.SINGLETON); + switchDevMode(props); } diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties index 571dcf5..57b692a 100644 --- a/core/src/main/resources/org/apache/struts2/default.properties +++ b/core/src/main/resources/org/apache/struts2/default.properties @@ -243,4 +243,10 @@ struts.handle.exception=true ### NOTE: The sample line below is *INTENTIONALLY* commented out, as this feature is disabled by default. # struts.ognl.expressionMaxLength=256 +### Defines which named instance of DateFormatter to use, there are two instances: +### - simpleDateFormatter (based on SimpleDateFormat) +### - dateTimeFormatter (based on Java 8 Date/Time API) +### These formatters are using a slightly different patterns, please check JavaDocs for from details and WW-5016 +struts.date.formatter=dateTimeFormatter + ### END SNIPPET: complete_file diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 9dd8fbf..96b17b4 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -216,7 +216,7 @@ <bean type="com.opensymphony.xwork2.UnknownHandlerManager" class="com.opensymphony.xwork2.DefaultUnknownHandlerManager" name="struts" /> <bean type="org.apache.struts2.dispatcher.DispatcherErrorHandler" name="struts" class="org.apache.struts2.dispatcher.DefaultDispatcherErrorHandler" /> - + <!-- Silly workarounds for OGNL since there is currently no way to flush its internal caches --> <bean type="ognl.PropertyAccessor" name="java.util.ArrayList" class="com.opensymphony.xwork2.ognl.accessor.XWorkListPropertyAccessor" /> <bean type="ognl.PropertyAccessor" name="java.util.HashSet" class="com.opensymphony.xwork2.ognl.accessor.XWorkCollectionPropertyAccessor" /> @@ -228,6 +228,9 @@ <bean type="com.opensymphony.xwork2.config.providers.ValueSubstitutor" class="com.opensymphony.xwork2.config.providers.EnvsValueSubstitutor" scope="singleton"/> + <bean type="org.apache.struts2.components.date.DateFormatter" name="simpleDateFormatter" class="org.apache.struts2.components.date.SimpleDateFormatAdapter" scope="singleton"/> + <bean type="org.apache.struts2.components.date.DateFormatter" name="dateTimeFormatter" class="org.apache.struts2.components.date.DateTimeFormatterAdapter" scope="singleton"/> + <package name="struts-default" abstract="true"> <result-types> <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> diff --git a/core/src/test/java/org/apache/struts2/components/DateTest.java b/core/src/test/java/org/apache/struts2/components/DateTest.java new file mode 100644 index 0000000..2b9ca8b --- /dev/null +++ b/core/src/test/java/org/apache/struts2/components/DateTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.components; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.util.ValueStack; +import com.opensymphony.xwork2.util.ValueStackFactory; +import org.apache.struts2.StrutsInternalTestCase; +import org.apache.struts2.components.date.SimpleDateFormatAdapter; + +import java.io.StringWriter; +import java.io.Writer; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Map; + +public class DateTest extends StrutsInternalTestCase { + + private Map<String, Object> context; + private ValueStack stack; + + public void testSupportSimpleDateTimeFormat() { + // given + Date date = new Date(stack); + date.setDateFormatter(new SimpleDateFormatAdapter()); + + String format = "EEEE MMMM dd, hh:mm aa"; + java.util.Date now = new java.util.Date(); + + String expected = new SimpleDateFormat(format, ActionContext.getContext().getLocale()).format(now); + context.put("myDate", now); + + Writer writer = new StringWriter(); + + // when + date.setFormat(format); + date.setName("myDate"); + date.setNice(false); + date.start(writer); + date.end(writer, ""); + + // then + assertEquals(expected, writer.toString()); + } + + public void testDefaultFormat() { + // given + Date date = new Date(stack); + date.setDateFormatter(new SimpleDateFormatAdapter()); + + java.util.Date now = new java.util.Date(); + + String expected = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM, ActionContext.getContext().getLocale()).format(now); + context.put("myDate", now); + + Writer writer = new StringWriter(); + + // when + date.setName("myDate"); + date.setNice(false); + date.start(writer); + date.end(writer, ""); + + // then + assertEquals(expected, writer.toString()); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + stack = container.getInstance(ValueStackFactory.class).createValueStack(); + context = stack.getContext(); + } +}