WW-5016 Introduces different format adapters to allow use different APIs

This commit is contained in:
Lukasz Lenart
2022-02-20 13:29:46 +01:00
parent 804e1546f1
commit e3dff7691e
10 changed files with 280 additions and 41 deletions
@@ -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());
@@ -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";
}
@@ -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;
@@ -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);
}
@@ -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);
}
}
@@ -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()));
}
}
@@ -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);
}
@@ -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
+4 -1
View File
@@ -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"/>
@@ -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();
}
}