Skip to content

Commit

Permalink
WW-5016 Introduces different format adapters to allow use different APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszlenart committed Feb 20, 2022
1 parent 804e154 commit e3dff76
Show file tree
Hide file tree
Showing 10 changed files with 280 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
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;
Expand Down Expand Up @@ -218,6 +221,9 @@ public void register(ContainerBuilder builder, LocatableProperties props)
, 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());
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/org/apache/struts2/StrutsConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.struts2;

import org.apache.struts2.components.date.DateFormatter;
import org.apache.struts2.dispatcher.mapper.CompositeActionMapper;

/**
Expand Down Expand Up @@ -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";
}
78 changes: 40 additions & 38 deletions core/src/main/java/org/apache/struts2/components/Date.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -33,8 +35,6 @@
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;
Expand Down Expand Up @@ -157,6 +157,7 @@
public class Date extends ContextBean {

private static final Logger LOG = LogManager.getLogger(Date.class);

/**
* Property name to fall back when no format is specified
*/
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -286,6 +288,8 @@ public String formatTime(TextProvider tp, ZonedDateTime date) {

@Override
public boolean end(Writer writer, String body) {
TextProvider textProvider = findProviderInStack();

ZonedDateTime date = null;
final ZoneId tz = getTimeZone();
// find the name on the valueStack
Expand All @@ -304,10 +308,9 @@ public boolean end(Writer writer, String body) {
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[]{
Expand All @@ -329,33 +332,11 @@ public boolean end(Writer writer, String body) {
}
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 {
Expand All @@ -373,6 +354,18 @@ public boolean end(Writer writer, String body) {
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) {
Expand All @@ -386,6 +379,15 @@ private ZoneId getTimeZone() {
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,12 @@
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;
Expand Down Expand Up @@ -422,6 +421,8 @@ public void register(ContainerBuilder builder, LocatableProperties props) {
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);
}

Expand Down
6 changes: 6 additions & 0 deletions core/src/main/resources/org/apache/struts2/default.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 4 additions & 1 deletion core/src/main/resources/struts-default.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand All @@ -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"/>
Expand Down
Loading

0 comments on commit e3dff76

Please sign in to comment.