Compare commits

..

25 Commits

Author SHA1 Message Date
Lukasz Lenart 05ca1ff087 Ties cache extension points with implementation 2022-05-02 09:23:31 +02:00
JCgH4164838Gh792C124B5 199f356669 Update:
- Added some IDE-recommended annotations and cleanup to some of the
  modified files.
- Applied easier-to-read/differentiate names "ognlExpressionCacheFactory"
  and "ognlBeanInfoCacheFactory" for the cache factory configuration
   extension points.
- Reorded default configuration factory init for the cache factories (did
  not help extension override).
- Cleanup of parameterized OgnlUtil constructor.
- Added extension point aliases to StrutsBeanSelectionProvider.
- Added beaninfo for cache factories to ConstantConfig (did not help
  extension override).
- Added cache factory references to default.properties, struts-default.xml.
2022-05-01 16:25:16 -04:00
JCgH4164838Gh792C124B5 084c66723d Update:
- Implement a no-parameter build method in OgnlCacheFactory.
- Update OgnlUtil to use no-parameter cache build method.
- Add an additional code coverage test.
2022-03-20 20:39:14 -04:00
JCgH4164838Gh792C124B5 4c37011020 Merge branch 'master' of https://github.com/JCgH4164838Gh792C124B5/struts into localS2_26_OgnlUtilOptionalCache1
# Conflicts:
#	core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java
#	core/src/main/resources/struts-default.xml

Manually resolved conflicts.
2022-03-06 21:51:33 -05:00
JCgH4164838Gh792C124B5 fbb31ee65b Update:
- Refactored the cache design to utilize a factory pattern.
- Updated unit tests to match refactoring.
2022-03-06 21:18:21 -05:00
Lukasz Lenart c094052969 Uses Java Reflections instead BeanUtils to read property 2022-03-04 12:52:33 +01:00
Lukasz Lenart 84164c422e Drops unused dependency 2022-03-03 12:18:11 +01:00
Lukasz Lenart 8b11a3873a WW-5171 Upgrades Log4j to version 2.17.2 2022-03-01 08:10:00 +01:00
Lukasz Lenart 115fef2af7 [maven-release-plugin] prepare for next development iteration 2022-02-24 07:11:22 +01:00
Lukasz Lenart 0c9f9c3400 [maven-release-plugin] prepare release STRUTS_6_0_0_RC3 2022-02-24 07:11:13 +01:00
Lukasz Lenart 3e65ec1687 Merge pull request #529 from apache/WW-5016-uses-proper-format
[WW-5016] Adds support for LocalDate and adjusts tests to use the new Java 8 API
2022-02-23 10:18:37 +01:00
Lukasz Lenart 59932a5179 WW-5016 Reduces calls to TextProvider 2022-02-21 19:31:52 +01:00
Lukasz Lenart 4746a49d17 WW-5016 Improves description of different patterns 2022-02-21 08:47:36 +01:00
Lukasz Lenart 059514d6ca Merge pull request #531 from apache/WW-5168-submit-unchecked
[WW-5168] Support submit unchecked in Javatemplates and fixes logic for FTL template
2022-02-20 16:58:26 +01:00
Lukasz Lenart 5648721d07 WW-5016 Fixes typo in instance name 2022-02-20 13:33:49 +01:00
Lukasz Lenart e3dff7691e WW-5016 Introduces different format adapters to allow use different APIs 2022-02-20 13:29:46 +01:00
Lukasz Lenart 38c63334a4 WW-4963 Adds missing references to the new versions 2022-02-20 10:37:25 +01:00
Lukasz Lenart fe7959e5d8 WW-5168 Adjusts tests to flipped if condition 2022-02-13 10:19:01 +01:00
Lukasz Lenart d454384a4b WW-5168 Reverts condition in if statement 2022-02-13 10:00:20 +01:00
Lukasz Lenart 1c3cab2e6f WW-5168 Fixes disabled attribute across all the Javatemplate tags 2022-02-10 18:42:10 +01:00
Lukasz Lenart b1e9eae01c WW-5168 Support submit unchecked in Javatemplates and fixes logic for all FTL template 2022-02-09 17:33:46 +01:00
Lukasz Lenart 804e1546f1 WW-5016 Adds support for LocalDate and adjusts tests to use the new Java 8 API 2022-02-06 12:23:39 +01:00
Lukasz Lenart f0b24d17da WW-5167 Upgrades Xstream to version 1.4.19 to address CVE-2021-43859 2022-02-06 11:26:51 +01:00
JCgH4164838Gh792C124B5 4d8108e766 Update:
- Add support for an optional basic LRU cache for OGNL expressions and
  OGNL BeanInfo.
- Add support for cache limits applying to both normal and LRU caches.
  For a normal cache the entire cache will flush when the limit is reached.
- Add flags to allow switching between normal and LRU caches, and setting
  the maximum sizes.
2022-01-30 20:57:59 -05:00
Lukasz Lenart 14c3a14c8f [maven-release-plugin] prepare for next development iteration 2022-01-29 17:36:34 +01:00
84 changed files with 1820 additions and 334 deletions
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-apps</artifactId>
<packaging>pom</packaging>
+2 -2
View File
@@ -24,12 +24,12 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-showcase</artifactId>
@@ -27,22 +27,24 @@ import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.interceptor.validation.SkipValidation;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.ScriptAssert;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.util.Date;
/**
* <!-- START SNIPPET: beanValidatationExample -->
*/
// <!-- START SNIPPET: beanValidationExample -->
@Namespace("/bean-validation")
@ParentPackage("bean-validation")
@Action(results = {
@Result(name = "input", location = "bean-validation.jsp"),
@Result(name = "success", location = "/WEB-INF/validation/successFieldValidatorsExample.jsp")
@Result(name = "input", location = "bean-validation.jsp"),
@Result(name = "success", location = "/WEB-INF/validation/successFieldValidatorsExample.jsp")
})
@FieldMatch(first = "fieldExpressionValidatorField", second = "requiredValidatorField", message = "requiredValidatorField and fieldExpressionValidatorField are not matching")
@ScriptAssert(lang = "javascript", script = "_this.dateValidatorField != null && _this.dateValidatorField.before(new java.util.Date())", message = "Date need to before now")
@@ -82,10 +84,10 @@ public class BeanValidationExampleAction extends ActionSupport {
private String fieldExpressionValidatorField = null;
@Action(value = "bean-validation", results = {
@Result(name = "success", location = "bean-validation.jsp")
@Result(name = "success", location = "bean-validation.jsp")
})
@SkipValidation
public String beanValidation(){
public String beanValidation() {
return SUCCESS;
}
@@ -150,7 +152,7 @@ public class BeanValidationExampleAction extends ActionSupport {
}
public void setFieldExpressionValidatorField(
String fieldExpressionValidatorField) {
String fieldExpressionValidatorField) {
this.fieldExpressionValidatorField = fieldExpressionValidatorField;
}
@@ -163,8 +165,4 @@ public class BeanValidationExampleAction extends ActionSupport {
}
}
/**
* <!-- END SNIPPET: beanValidatationExample -->
*/
// <!-- END SNIPPET: beanValidationExample -->
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-assembly</artifactId>
+3 -3
View File
@@ -29,7 +29,7 @@
</parent>
<artifactId>struts2-bom</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Struts 2 Bill of Materials</name>
@@ -44,7 +44,7 @@
</licenses>
<properties>
<struts-version.version>6.0.0-RC2</struts-version.version>
<struts-version.version>6.0.0-SNAPSHOT</struts-version.version>
<maven.site.skip>true</maven.site.skip>
<maven.site.deploy.skip>true</maven.site.deploy.skip>
</properties>
@@ -175,7 +175,7 @@
</dependencyManagement>
<scm>
<tag>STRUTS_6_0_0_RC2</tag>
<tag>HEAD</tag>
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-osgi-bundles</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-osgi-admin-bundle</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-osgi-bundles</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-osgi-demo-bundle</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-osgi-bundles</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-core</artifactId>
<packaging>jar</packaging>
@@ -28,6 +28,10 @@ import com.opensymphony.xwork2.conversion.*;
import com.opensymphony.xwork2.conversion.impl.*;
import com.opensymphony.xwork2.factory.*;
import com.opensymphony.xwork2.inject.*;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
import com.opensymphony.xwork2.ognl.OgnlUtil;
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
@@ -79,30 +83,37 @@ public class DefaultConfiguration implements Configuration {
}
@Override
public PackageConfig getPackageConfig(String name) {
return packageContexts.get(name);
}
@Override
public List<UnknownHandlerConfig> getUnknownHandlerStack() {
return unknownHandlerStack;
}
@Override
public void setUnknownHandlerStack(List<UnknownHandlerConfig> unknownHandlerStack) {
this.unknownHandlerStack = unknownHandlerStack;
}
@Override
public Set<String> getPackageConfigNames() {
return packageContexts.keySet();
}
@Override
public Map<String, PackageConfig> getPackageConfigs() {
return packageContexts;
}
@Override
public Set<String> getLoadedFileNames() {
return loadedFileNames;
}
@Override
public RuntimeConfiguration getRuntimeConfiguration() {
return runtimeConfiguration;
}
@@ -110,10 +121,12 @@ public class DefaultConfiguration implements Configuration {
/**
* @return the container
*/
@Override
public Container getContainer() {
return container;
}
@Override
public void addPackageConfig(String name, PackageConfig packageContext) {
PackageConfig check = packageContexts.get(name);
if (check != null) {
@@ -131,6 +144,7 @@ public class DefaultConfiguration implements Configuration {
packageContexts.put(name, packageContext);
}
@Override
public PackageConfig removePackageConfig(String packageName) {
return packageContexts.remove(packageName);
}
@@ -138,11 +152,13 @@ public class DefaultConfiguration implements Configuration {
/**
* Allows the configuration to clean up any resources used
*/
@Override
public void destroy() {
packageContexts.clear();
loadedFileNames.clear();
}
@Override
public void rebuildRuntimeConfiguration() {
runtimeConfiguration = buildRuntimeConfiguration();
}
@@ -151,10 +167,12 @@ public class DefaultConfiguration implements Configuration {
* Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls
* buildRuntimeConfiguration().
*
* @param providers list of ContainerProvider
* @return list of package providers
*
* @throws ConfigurationException in case of any configuration errors
*/
@Override
public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
packageContexts.clear();
loadedFileNames.clear();
@@ -172,6 +190,7 @@ public class DefaultConfiguration implements Configuration {
props.setConstants(builder);
builder.factory(Configuration.class, new Factory<Configuration>() {
@Override
public Configuration create(Context context) throws Exception {
return DefaultConfiguration.this;
}
@@ -279,6 +298,8 @@ public class DefaultConfiguration implements Configuration {
builder.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON);
builder.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON);
builder.factory(ExpressionCacheFactory.class, "defaultOgnlExpressionCacheFactory", DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON);
builder.factory(BeanInfoCacheFactory.class, "defaultOgnlBeanInfoCacheFactory", DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON);
builder.factory(OgnlUtil.class, Scope.SINGLETON);
builder.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON);
@@ -417,10 +438,10 @@ public class DefaultConfiguration implements Configuration {
private static class RuntimeConfigurationImpl implements RuntimeConfiguration {
private Map<String, Map<String, ActionConfig>> namespaceActionConfigs;
private Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
private NamespaceMatcher namespaceMatcher;
private Map<String, String> namespaceConfigs;
private final Map<String, Map<String, ActionConfig>> namespaceActionConfigs;
private final Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
private final NamespaceMatcher namespaceMatcher;
private final Map<String, String> namespaceConfigs;
public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs,
Map<String, String> namespaceConfigs,
@@ -448,6 +469,7 @@ public class DefaultConfiguration implements Configuration {
* @param namespace the namespace for the action or null for the empty namespace, ""
* @return the configuration information for action requested
*/
@Override
public ActionConfig getActionConfig(String namespace, String name) {
ActionConfig config = findActionConfigInNamespace(namespace, name);
@@ -503,6 +525,7 @@ public class DefaultConfiguration implements Configuration {
*
* @return a Map of namespace - > Map of ActionConfig objects, with the key being the action name
*/
@Override
public Map<String, Map<String, ActionConfig>> getActionConfigs() {
return namespaceActionConfigs;
}
@@ -26,6 +26,8 @@ import com.opensymphony.xwork2.StrutsTextProviderFactory;
import com.opensymphony.xwork2.TextProviderFactory;
import com.opensymphony.xwork2.factory.DefaultUnknownHandlerFactory;
import com.opensymphony.xwork2.factory.UnknownHandlerFactory;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.accessor.HttpParametersPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.ParameterPropertyAccessor;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
@@ -56,6 +58,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;
@@ -91,6 +96,8 @@ import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory;
import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.PatternMatcher;
@@ -127,19 +134,24 @@ import java.util.Set;
public class StrutsDefaultConfigurationProvider implements ConfigurationProvider {
@Override
public void destroy() {
}
@Override
public void init(Configuration configuration) throws ConfigurationException {
}
@Override
public void loadPackages() throws ConfigurationException {
}
@Override
public boolean needsReload() {
return false;
}
@Override
public void register(ContainerBuilder builder, LocatableProperties props)
throws ConfigurationException {
@@ -205,6 +217,8 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
.factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON)
.factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON)
.factory(ExpressionCacheFactory.class, "defaultOgnlExpressionCacheFactory", DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON)
.factory(BeanInfoCacheFactory.class, "defaultOgnlBeanInfoCacheFactory", DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON)
.factory(OgnlUtil.class, Scope.SINGLETON)
.factory(CollectionConverter.class, Scope.SINGLETON)
.factory(ArrayConverter.class, Scope.SINGLETON)
@@ -218,6 +232,9 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
, Scope.SINGLETON)
.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON)
.factory(DateFormatter.class, "simpleDateFormatter", SimpleDateFormatAdapter.class, Scope.SINGLETON)
.factory(DateFormatter.class, "dateTimeFormatter", DateTimeFormatterAdapter.class, Scope.SINGLETON)
;
props.setProperty(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, Boolean.FALSE.toString());
@@ -38,7 +38,8 @@ import java.lang.annotation.Target;
*
* <p>
* Application wide conversion:<br>
* The conversion rules will be assembled within the <code>xwork-conversion.properties</code> file within the classpath root.
* The conversion rules will be assembled within the <code>struts-conversion.properties</code> or
* <code>xwork-conversion.properties</code> (deprecated) file within the classpath root.
* Set type to: <code>type = ConversionType.APPLICATION</code>
* </p>
* <!-- END SNIPPET: description -->
@@ -0,0 +1,23 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
/**
* A proxy interface to be used with Struts DI mechanism
*/
public interface BeanInfoCacheFactory<Key, Value> extends OgnlCacheFactory <Key, Value> {
}
@@ -0,0 +1,44 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.struts2.StrutsConstants;
/**
* Default OGNL Cache factory implementation.
*
* Currently used for BeanInfo cache creation.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class DefaultOgnlBeanInfoCacheFactory<Key, Value> extends DefaultOgnlCacheFactory<Key, Value>
implements BeanInfoCacheFactory<Key, Value> {
@Override
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE, required = false)
protected void setCacheMaxSize(String maxSize) {
super.setCacheMaxSize(maxSize);
}
@Override
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_LRU_MODE, required = false)
protected void setUseLRUCache(String useLRUMode) {
super.setUseLRUCache(useLRUMode);
}
}
@@ -0,0 +1,66 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.BooleanUtils;
/**
* Default OGNL Cache factory implementation.
*
* Currently used for Expression cache and BeanInfo cache creation.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class DefaultOgnlCacheFactory<Key, Value> implements OgnlCacheFactory<Key, Value> {
private final AtomicBoolean useLRUCache = new AtomicBoolean(false);
private final AtomicInteger cacheMaxSize = new AtomicInteger(25000);
@Override
public OgnlCache<Key, Value> buildOgnlCache() {
return buildOgnlCache(getCacheMaxSize(), 16, 0.75f, getUseLRUCache());
}
@Override
public OgnlCache<Key, Value> buildOgnlCache(int evictionLimit, int initialCapacity, float loadFactor, boolean lruCache) {
if (lruCache) {
return new OgnlLRUCache<>(evictionLimit, initialCapacity, loadFactor);
} else {
return new OgnlDefaultCache<>(evictionLimit, initialCapacity, loadFactor);
}
}
@Override
public int getCacheMaxSize() {
return cacheMaxSize.get();
}
protected void setCacheMaxSize(String maxSize) {
cacheMaxSize.set(Integer.parseInt(maxSize));
}
@Override
public boolean getUseLRUCache() {
return useLRUCache.get();
}
protected void setUseLRUCache(String useLRUMode) {
useLRUCache.set(BooleanUtils.toBoolean(useLRUMode));
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.struts2.StrutsConstants;
/**
* Default OGNL Expression Cache factory implementation.
*
* Currently used for Expression cache creation.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class DefaultOgnlExpressionCacheFactory<Key, Value> extends DefaultOgnlCacheFactory<Key, Value>
implements ExpressionCacheFactory<Key, Value> {
@Override
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, required = false)
protected void setCacheMaxSize(String maxSize) {
super.setCacheMaxSize(maxSize);
}
@Override
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_LRU_MODE, required = false)
protected void setUseLRUCache(String useLRUMode) {
super.setUseLRUCache(useLRUMode);
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
/**
* A proxy interface to be used with Struts DI mechanism
*/
public interface ExpressionCacheFactory<Key, Value> extends OgnlCacheFactory <Key, Value> {
}
@@ -0,0 +1,41 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
/**
* A basic cache interface for use with OGNL processing (such as Expression, BeanInfo).
* All OGNL caches will have an eviction limit, but setting an extremely high value can
* simulate an "effectively unlimited" cache.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public interface OgnlCache<Key, Value> {
public Value get(Key key);
public void put(Key key, Value value);
public void putIfAbsent(Key key, Value value);
public int size();
public void clear();
public int getEvictionLimit();
public void setEvictionLimit(int cacheEvictionLimit);
}
@@ -0,0 +1,30 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
/**
* Used by {@link com.opensymphony.xwork2.ognl.OgnlUtil} to create appropriate OGNL
* caches based on configuration.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
interface OgnlCacheFactory<Key, Value> {
OgnlCache<Key, Value> buildOgnlCache();
OgnlCache<Key, Value> buildOgnlCache(int evictionLimit, int initialCapacity, float loadFactor, boolean lruCache);
int getCacheMaxSize();
boolean getUseLRUCache();
}
@@ -0,0 +1,85 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Default OGNL cache implementation.
*
* Setting a very high eviction limit simulates an unlimited cache.
* Setting too low an eviction limit will make the cache ineffective.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class OgnlDefaultCache<Key, Value> implements OgnlCache<Key, Value> {
private final ConcurrentHashMap<Key, Value> ognlCache;
private final AtomicInteger cacheEvictionLimit = new AtomicInteger(25000);
public OgnlDefaultCache(int evictionLimit, int initialCapacity, float loadFactor) {
this.cacheEvictionLimit.set(evictionLimit);
ognlCache = new ConcurrentHashMap<>(initialCapacity, loadFactor);
}
@Override
public Value get(Key key) {
return ognlCache.get(key);
}
@Override
public void put(Key key, Value value) {
ognlCache.put(key, value);
this.clearIfEvictionLimitExceeded();
}
@Override
public void putIfAbsent(Key key, Value value) {
ognlCache.putIfAbsent(key, value);
this.clearIfEvictionLimitExceeded();
}
@Override
public int size() {
return ognlCache.size();
}
@Override
public void clear() {
ognlCache.clear();
}
@Override
public int getEvictionLimit() {
return this.cacheEvictionLimit.get();
}
@Override
public void setEvictionLimit(int cacheEvictionLimit) {
this.cacheEvictionLimit.set(cacheEvictionLimit);
}
/**
* Clear the cache if the eviction limit has been exceeded.
*/
private void clearIfEvictionLimitExceeded() {
if (ognlCache.size() > cacheEvictionLimit.get()) {
ognlCache.clear();
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2022 Apache Software Foundation.
*
* Licensed 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 com.opensymphony.xwork2.ognl;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A basic OGNL LRU cache implementation.
*
* The implementation utilizes a {@link Collections#synchronizedMap(java.util.Map)}
* backed by a {@link LinkedHashMap}. May be replaced by a more efficient implementation in the future.
*
* Setting too low an eviction limit will produce more overhead than value.
* Setting too high an eviction limit may also produce more overhead than value.
* An appropriate eviction limit will need to be determined on an individual application basis.
*
* @param <Key> The type for the cache key entries
* @param <Value> The type for the cache value entries
*/
public class OgnlLRUCache<Key, Value> implements OgnlCache<Key, Value> {
private final Map<Key, Value> ognlLRUCache;
private final AtomicInteger cacheEvictionLimit = new AtomicInteger(2500);
public OgnlLRUCache(int evictionLimit, int initialCapacity, float loadFactor) {
this.cacheEvictionLimit.set(evictionLimit);
// Access-order mode selected (order mode true in LinkedHashMap constructor).
ognlLRUCache = Collections.synchronizedMap (new LinkedHashMap<Key, Value>(initialCapacity, loadFactor, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Key,Value> eldest) {
return (this.size() > cacheEvictionLimit.get());
}
});
}
@Override
public Value get(Key key) {
return ognlLRUCache.get(key);
}
@Override
public void put(Key key, Value value) {
ognlLRUCache.put(key, value);
}
@Override
public void putIfAbsent(Key key, Value value) {
ognlLRUCache.putIfAbsent(key, value);
}
@Override
public int size() {
return ognlLRUCache.size();
}
@Override
public void clear() {
ognlLRUCache.clear();
}
@Override
public int getEvictionLimit() {
return this.cacheEvictionLimit.get();
}
@Override
public void setEvictionLimit(int cacheEvictionLimit) {
this.cacheEvictionLimit.set(cacheEvictionLimit);
}
}
@@ -38,8 +38,6 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
@@ -57,8 +55,8 @@ public class OgnlUtil {
// Flag used to reduce flooding logs with WARNs about using DevMode excluded packages
private final AtomicBoolean warnReported = new AtomicBoolean(false);
private final ConcurrentMap<String, Object> expressions = new ConcurrentHashMap<>();
private final ConcurrentMap<Class<?>, BeanInfo> beanInfoCache = new ConcurrentHashMap<>();
private final OgnlCache<String, Object> expressionCache;
private final OgnlCache<Class<?>, BeanInfo> beanInfoCache;
private TypeConverter defaultConverter;
private boolean devMode;
@@ -78,7 +76,31 @@ public class OgnlUtil {
private boolean allowStaticMethodAccess;
private boolean disallowProxyMemberAccess;
/**
* Construct a new OgnlUtil instance for use with the framework
*
* @deprecated It is recommended to utilize the {@link OgnlUtil#OgnlUtil(com.opensymphony.xwork2.ognl.ExpressionCacheFactory, com.opensymphony.xwork2.ognl.BeanInfoCacheFactory) method instead.
*/
@Deprecated
public OgnlUtil() {
this(null, null); // Instantiate default Expression and BeanInfo caches (null factories)
}
/**
* Construct a new OgnlUtil instance for use with the framework, with optional
* cache factories for OGNL Expression and BeanInfo caches.
*
* NOTE: Although the extension points are defined for the optional cache factories, developer-defined overrides do
* do not appear to function at this time (it always appears to instantiate the default factories).
* Construction injectors do not allow the optional flag, so the definitions must be defined.
*
* @param ognlExpressionCacheFactory factory for Expression cache instance. If null, it uses a default
* @param ognlBeanInfoCacheFactory factory for BeanInfo cache instance. If null, it uses a default
*/
public OgnlUtil(
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, required = false) ExpressionCacheFactory<String, Object> ognlExpressionCacheFactory,
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, required = false) BeanInfoCacheFactory<Class<?>, BeanInfo> ognlBeanInfoCacheFactory
) {
excludedClasses = Collections.unmodifiableSet(new HashSet<>());
excludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>());
excludedPackageNames = Collections.unmodifiableSet(new HashSet<>());
@@ -86,6 +108,12 @@ public class OgnlUtil {
devModeExcludedClasses = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedPackageNames = Collections.unmodifiableSet(new HashSet<>());
OgnlCacheFactory<String, Object> ognlExpressionCacheFactory1 = (ognlExpressionCacheFactory != null ? ognlExpressionCacheFactory : new DefaultOgnlExpressionCacheFactory<>());
OgnlCacheFactory<Class<?>, BeanInfo> ognlBeanInfoCacheFactory1 = (ognlBeanInfoCacheFactory != null ? ognlBeanInfoCacheFactory : new DefaultOgnlBeanInfoCacheFactory<>());
this.expressionCache = ognlExpressionCacheFactory1.buildOgnlCache();
this.beanInfoCache = ognlBeanInfoCacheFactory1.buildOgnlCache();
}
@Inject
@@ -103,6 +131,16 @@ public class OgnlUtil {
enableExpressionCache = BooleanUtils.toBoolean(cache);
}
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE, required = false)
protected void setExpressionCacheMaxSize(String maxSize) {
expressionCache.setEvictionLimit(Integer.parseInt(maxSize));
}
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE, required = false)
protected void setBeanInfoCacheMaxSize(String maxSize) {
beanInfoCache.setEvictionLimit(Integer.parseInt(maxSize));
}
@Inject(value = StrutsConstants.STRUTS_OGNL_ENABLE_EVAL_EXPRESSION, required = false)
protected void setEnableEvalExpression(String evalExpression) {
this.enableEvalExpression = BooleanUtils.toBoolean(evalExpression);
@@ -273,7 +311,7 @@ public class OgnlUtil {
* @since 2.5.21
*/
public void clearExpressionCache() {
expressions.clear();
expressionCache.clear();
}
/**
@@ -284,7 +322,7 @@ public class OgnlUtil {
* @since 2.5.21
*/
public int expressionCacheSize() {
return expressions.size();
return expressionCache.size();
}
/**
@@ -532,11 +570,11 @@ public class OgnlUtil {
private <T> Object compileAndExecute(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {
Object tree;
if (enableExpressionCache) {
tree = expressions.get(expression);
tree = expressionCache.get(expression);
if (tree == null) {
tree = Ognl.parseExpression(expression);
checkEnableEvalExpression(tree, context);
expressions.putIfAbsent(expression, tree);
expressionCache.putIfAbsent(expression, tree);
}
} else {
tree = Ognl.parseExpression(expression);
@@ -549,11 +587,11 @@ public class OgnlUtil {
private <T> Object compileAndExecuteMethod(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {
Object tree;
if (enableExpressionCache) {
tree = expressions.get(expression);
tree = expressionCache.get(expression);
if (tree == null) {
tree = Ognl.parseExpression(expression);
checkSimpleMethod(tree, context);
expressions.putIfAbsent(expression, tree);
expressionCache.putIfAbsent(expression, tree);
}
} else {
tree = Ognl.parseExpression(expression);
@@ -18,6 +18,7 @@
*/
package org.apache.struts2;
import org.apache.struts2.components.date.DateFormatter;
import org.apache.struts2.dispatcher.mapper.CompositeActionMapper;
/**
@@ -252,6 +253,47 @@ public final class StrutsConstants {
/** Throw RuntimeException when a property is not found, or the evaluation of the expression fails */
public static final String STRUTS_EL_THROW_EXCEPTION = "struts.el.throwExceptionOnFailure";
/**
* Specifies an OGNL expression cache factory implementation. A default implementation is provided, but
* could be replaced by a custom one if desired.
*
* @since 2.6
*/
public static final String STRUTS_OGNL_EXPRESSION_CACHE_FACTORY = "struts.ognl.expressionCacheFactory";
/**
* Specifies an OGNL BeanInfo cache factory implementation. A default implementation is provided, but
* could be replaced by a custom one if desired.
*
* @since 2.6
*/
public static final String STRUTS_OGNL_BEANINFO_CACHE_FACTORY = "struts.ognl.beanInfoCacheFactory";
/**
* Specifies a maximum number of cached BeanInfo used by OgnlUtility. Not specified/set by default. If
* a positive integer is specified, it will set a limit whose behaviour depends on whether the
* normal (default) cache or optional LRU cache is in place.
*
* For the normal (default) cache, exceeding the maximum will cause the entire cache to flush (clear).
* For the optional LRU cache, once the maximum is reached, the least-recently-used (LRU) entry will be
* removed when a new entry needs to be added (cache is fully-utilized).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE = "struts.ognl.beanInfoCacheMaxSize";
/**
* Set the cache mode of the BeanInfo cache used by OgnlUtility. A value of true means enable
* least-recently-used (LRU) mode, a value of false (or any non-true value) means to use the
* default cache.
*
* Note: When enabling LRU cache mode you must also set a maximum size (via {@link #STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE})
* for it to be effective. Otherwise, there is no condition to evict a LRU entry (cache has no limit).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_BEANINFO_CACHE_LRU_MODE = "struts.ognl.beanInfoCacheLRUMode";
/**
* Logs properties that are not found (very verbose)
* @since 2.6
@@ -277,6 +319,31 @@ public final class StrutsConstants {
*/
public static final String STRUTS_ENABLE_OGNL_EXPRESSION_CACHE = STRUTS_OGNL_ENABLE_EXPRESSION_CACHE;
/**
* Specifies a maximum number of cached parsed OGNL expressions. Not specified/set by default. If
* a positive integer is specified, it will set a limit whose behaviour depends on whether the
* normal (default) cache or optional LRU cache is in place.
*
* For the normal (default) cache, exceeding the maximum will cause the entire cache to flush (clear).
* For the optional LRU cache, once the maximum is reached, the least-recently-used (LRU) entry will be
* removed when a new entry needs to be added (cache is fully-utilized).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE = "struts.ognl.expressionCacheMaxSize";
/**
* Set the cache mode of the parsed OGNL expression cache. A value of true means enable
* least-recently-used (LRU) mode, a value of false (or any non-true value) means to use the
* default cache.
*
* Note: When enabling LRU cache mode you must also set a maximum size (via {@link #STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE})
* for it to be effective. Otherwise, there is no condition to evict a LRU entry (cache has no limit).
*
* @since 2.6
*/
public static final String STRUTS_OGNL_EXPRESSION_CACHE_LRU_MODE = "struts.ognl.expressionCacheLRUMode";
/**
* Enables evaluation of OGNL expressions
* @since 2.6
@@ -384,4 +451,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";
}
@@ -75,7 +75,8 @@ public class Checkbox extends UIBean {
}
if (submitUnchecked != null) {
addParameter("submitUnchecked", findValue(submitUnchecked, Boolean.class));
Object parsedValue = findValue(submitUnchecked, Boolean.class);
addParameter("submitUnchecked", parsedValue == null ? Boolean.valueOf(submitUnchecked) : parsedValue);
} else {
addParameter("submitUnchecked", false);
}
@@ -384,9 +384,8 @@ public class Component {
return expression;
}
} else {
expression = stripExpression(expression);
return getStack().findValue(expression, toType, throwExceptionOnELFailure);
String strippedExpression = stripExpression(expression);
return getStack().findValue(strippedExpression, toType, throwExceptionOnELFailure);
}
}
@@ -18,29 +18,29 @@
*/
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.components.date.DateFormatter;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import java.io.IOException;
import java.io.Writer;
import java.time.Instant;
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;
/**
* <!-- START SNIPPET: javadoc -->
*
* <p>
* Format Date object in different ways.
* <p>
* The date tag will allow you to format a Date in a quick and easy way.
@@ -59,6 +59,12 @@ import java.util.List;
* </p>
*
* <p>
* <b>Note</b>: Since Struts 2.6 a new Java 8 API has been used to format the Date, it's based on
* <a href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>
* which uses a bit different patterns.
* </p>
*
* <p>
* Configurable attributes are:
* </p>
*
@@ -130,8 +136,8 @@ import java.util.List;
* <td>if one is not found DateFormat.MEDIUM format will be used</td>
* </tr>
* </table>
*
*
* <p>
* <p>
* <!-- END SNIPPET: javadoc -->
*
* <p><b>Examples</b></p>
@@ -145,12 +151,12 @@ import java.util.List;
* </pre>
*
* <code>Date</code>
*
*/
@StrutsTag(name="date", tldBodyContent="empty", tldTagClass="org.apache.struts2.views.jsp.DateTag", description="Render a formatted date.")
@StrutsTag(name = "date", tldBodyContent = "empty", tldTagClass = "org.apache.struts2.views.jsp.DateTag", description = "Render a formatted date.")
public class Date extends ContextBean {
private static final Logger LOG = LogManager.getLogger(Date.class);
/**
* Property name to fall back when no format is specified
*/
@@ -202,17 +208,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;
}
/**
@@ -280,6 +287,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
@@ -292,26 +301,27 @@ public class Date extends ContextBean {
date = Instant.ofEpochMilli((long) dateObject).atZone(tz);
} else if (dateObject instanceof LocalDateTime) {
date = ((LocalDateTime) dateObject).atZone(tz);
} else if (dateObject instanceof LocalDate) {
date = ((LocalDate) dateObject).atStartOfDay(tz);
} else if (dateObject instanceof Instant) {
date = ((Instant) dateObject).atZone(tz);
} else {
if (devMode) {
TextProvider tp = findProviderInStack();
String developerNotification = "";
if (tp != null) {
developerNotification = findProviderInStack().getText(
"devmode.notification",
"Developer Notification:\n{0}",
new String[]{
"Expression [" + name + "] passed to <s:date/> tag which was evaluated to [" + dateObject + "]("
+ (dateObject != null ? dateObject.getClass() : "null") + ") isn't supported!"
}
if (textProvider != null) {
developerNotification = textProvider.getText(
"devmode.notification",
"Developer Notification:\n{0}",
new String[]{
"Expression [" + name + "] passed to <s:date/> tag which was evaluated to [" + dateObject + "]("
+ (dateObject != null ? dateObject.getClass() : "null") + ") isn't supported!"
}
);
}
LOG.warn(developerNotification);
} else {
LOG.debug("Expression [{}] passed to <s:date/> tag which was evaluated to [{}]({}) isn't supported!",
name, dateObject, (dateObject != null ? dateObject.getClass() : "null"));
name, dateObject, (dateObject != null ? dateObject.getClass() : "null"));
}
}
@@ -321,33 +331,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 {
@@ -365,6 +353,20 @@ public class Date extends ContextBean {
return super.end(writer, "");
}
private String formatDate(TextProvider textProvider, ZonedDateTime date) {
String useFormat = format;
if (useFormat == null) {
// if the format is not specified, fall back using the defined property DATETAG_PROPERTY
useFormat = textProvider.getText(DATETAG_PROPERTY);
if (DATETAG_PROPERTY.equals(useFormat)) {
// if tp.getText can not find the property then the
// returned string is the same as input = DATETAG_PROPERTY
useFormat = null;
}
}
return dateFormatter.format(date, useFormat);
}
private ZoneId getTimeZone() {
ZoneId tz = ZoneId.systemDefault();
if (timezone != null) {
@@ -378,17 +380,26 @@ public class Date extends ContextBean {
return tz;
}
@StrutsTagAttribute(description="Date or DateTime format pattern", rtexprvalue=false)
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;
}
@StrutsTagAttribute(description="Whether to print out the date nicely", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether to print out the date nicely", type = "Boolean", defaultValue = "false")
public void setNice(boolean nice) {
this.nice = nice;
}
@StrutsTagAttribute(description = "The specific timezone in which to format the date", required = false)
@StrutsTagAttribute(description = "The specific timezone in which to format the date")
public void setTimezone(String timezone) {
this.timezone = timezone;
}
@@ -400,7 +411,7 @@ public class Date extends ContextBean {
return name;
}
@StrutsTagAttribute(description="The date value to format", required=true)
@StrutsTagAttribute(description = "The date value to format", required = true)
public void setName(String name) {
this.name = name;
}
@@ -702,11 +702,13 @@ public abstract class UIBean extends Component {
}
if (requiredLabel != null) {
addParameter("required", findValue(requiredLabel, Boolean.class));
Object parsedValue = findValue(requiredLabel, Boolean.class);
addParameter("required", parsedValue == null ? Boolean.valueOf(requiredLabel) : parsedValue);
}
if (disabled != null) {
addParameter("disabled", findValue(disabled, Boolean.class));
Object parsedValue = findValue(disabled, Boolean.class);
addParameter("disabled", parsedValue == null ? Boolean.valueOf(disabled) : parsedValue);
}
if (tabindex != null) {
@@ -886,9 +888,9 @@ public abstract class UIBean extends Component {
this.addParameter("tooltipDelay", findString(this.tooltipDelay));
if (this.javascriptTooltip != null) {
Boolean jsTooltips = (Boolean) findValue(this.javascriptTooltip, Boolean.class);
Object jsTooltips = findValue(this.javascriptTooltip, Boolean.class);
//TODO use a Boolean model when tooltipConfig is dropped
this.addParameter("jsTooltipEnabled", jsTooltips.toString());
this.addParameter("jsTooltipEnabled", jsTooltips == null ? this.javascriptTooltip : jsTooltips.toString());
if (form != null)
form.addParameter("hasTooltip", jsTooltips);
@@ -968,7 +970,7 @@ public abstract class UIBean extends Component {
// 1] UI component's tooltipConfig attribute OR
// 2] <param name="tooltip" value="" /> param tag value attribute
result = new LinkedHashMap<>((Map) tooltipConfigObj);
result = new LinkedHashMap<String, String>((Map) tooltipConfigObj);
} else if (tooltipConfigObj instanceof String) {
// we get this if its configured using
@@ -0,0 +1,40 @@
/*
* 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
* <p>
* New instance will be injected using {@link org.apache.struts2.StrutsConstants#STRUTS_DATE_FORMATTER}
*/
public interface DateFormatter {
/**
* Formats provided temporal with the given format
*
* @param temporal Java 8 {@link TemporalAccessor}
* @param format implementation specific format
* @return a string representation of the formatted `temporal`
*/
String format(TemporalAccessor temporal, String format);
}
@@ -0,0 +1,43 @@
/*
* 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) {
DateTimeFormatter dtf;
Locale locale = ActionContext.getContext().getLocale();
if (format == null) {
dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
.withLocale(locale);
} else {
dtf = DateTimeFormatter.ofPattern(format, locale);
}
return dtf.format(temporal);
}
}
@@ -0,0 +1,44 @@
/*
* 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) {
DateFormat df;
Locale locale = ActionContext.getContext().getLocale();
if (format == null) {
df = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM, locale);
} else {
df = new SimpleDateFormat(format, locale);
}
return df.format(new Date(Instant.from(temporal).toEpochMilli()));
}
}
@@ -23,6 +23,8 @@ import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.TextProviderFactory;
import com.opensymphony.xwork2.factory.UnknownHandlerFactory;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.FileManager;
@@ -54,13 +56,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;
@@ -367,6 +368,7 @@ import org.apache.struts2.views.util.UrlHelper;
*/
public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
@Override
public void register(ContainerBuilder builder, LocatableProperties props) {
alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props);
alias(ActionFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY_ACTIONFACTORY, builder, props);
@@ -422,6 +424,11 @@ 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);
alias(ExpressionCacheFactory.class, StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, builder, props, Scope.SINGLETON);
alias(BeanInfoCacheFactory.class, StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, builder, props, Scope.SINGLETON);
switchDevMode(props);
}
@@ -144,6 +144,8 @@ public class ConstantConfig {
private Boolean disallowProxyMemberAccess;
private Integer ognlAutoGrowthCollectionLimit;
private String staticContentPath;
private BeanConfig expressionCacheFactory;
private BeanConfig beaninfoCacheFactory;
protected String beanConfToString(BeanConfig beanConf) {
return beanConf == null ? null : beanConf.getName();
@@ -274,6 +276,8 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Objects.toString(disallowProxyMemberAccess, null));
map.put(StrutsConstants.STRUTS_OGNL_AUTO_GROWTH_COLLECTION_LIMIT, Objects.toString(ognlAutoGrowthCollectionLimit, null));
map.put(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, Objects.toString(staticContentPath, StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH));
map.put(StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, beanConfToString(expressionCacheFactory));
map.put(StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, beanConfToString(beaninfoCacheFactory));
return map;
}
@@ -1341,4 +1345,28 @@ public class ConstantConfig {
public void setStaticContentPath(String staticContentPath) {
this.staticContentPath = StaticContentLoader.Validator.validateStaticContentPath(staticContentPath);
}
public BeanConfig getExpressionCacheFactory() {
return expressionCacheFactory;
}
public void setExpressionCacheFactory(BeanConfig expressionCacheFactory) {
this.expressionCacheFactory = expressionCacheFactory;
}
public void setExpressionCacheFactory(Class<?> clazz) {
this.expressionCacheFactory = new BeanConfig(clazz, clazz.getName());
}
public BeanConfig getBeaninfoCacheFactory() {
return beaninfoCacheFactory;
}
public void setBeaninfoCacheFactory(BeanConfig beaninfoCacheFactory) {
this.beaninfoCacheFactory = beaninfoCacheFactory;
}
public void setBeaninfoCacheFactory(Class<?> clazz) {
this.beaninfoCacheFactory = new BeanConfig(clazz, clazz.getName());
}
}
@@ -45,25 +45,23 @@ import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
* </p>
*
* <ul>
*
* <li>{@link ServletContextAware}</li>
*
* <li>{@link org.apache.struts2.action.ServletContextAware}</li>
* <li>{@link ServletRequestAware}</li>
*
* <li>{@link org.apache.struts2.action.ServletRequestAware}</li>
* <li>{@link ServletResponseAware}</li>
*
* <li>{@link org.apache.struts2.action.ServletResponseAware}</li>
* <li>{@link ParameterAware} - deprecated since 2.5.4, please use {@link HttpParametersAware}</li>
*
* <li>{@link HttpParametersAware}</li>
*
* <li>{@link org.apache.struts2.action.ParametersAware}</li>
* <li>{@link RequestAware}</li>
*
* <li>{@link org.apache.struts2.action.ServletRequestAware}</li>
* <li>{@link SessionAware}</li>
*
* <li>{@link org.apache.struts2.action.SessionAware}</li>
* <li>{@link ApplicationAware}</li>
*
* <li>{@link org.apache.struts2.action.ApplicationAware}</li>
* <li>{@link PrincipalAware}</li>
*
* <li>{@link org.apache.struts2.action.PrincipalAware}</li>
* </ul>
*
* <!-- END SNIPPET: description -->
@@ -101,13 +99,19 @@ import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
* </pre>
*
* @see ServletContextAware
* @see org.apache.struts2.action.ServletContextAware
* @see ServletRequestAware
* @see org.apache.struts2.action.ServletRequestAware
* @see ServletResponseAware
* @see org.apache.struts2.action.ServletResponseAware
* @see ParameterAware
* @see ParametersAware
* @see org.apache.struts2.action.ParametersAware
* @see SessionAware
* @see org.apache.struts2.action.SessionAware
* @see ApplicationAware
* @see org.apache.struts2.action.ApplicationAware
* @see PrincipalAware
* @see org.apache.struts2.action.PrincipalAware
*/
public class ServletConfigInterceptor extends AbstractInterceptor implements StrutsStatics {
@@ -159,7 +163,7 @@ public class ServletConfigInterceptor extends AbstractInterceptor implements Str
if (action instanceof ApplicationAware) {
((ApplicationAware) action).setApplication(context.getApplication());
}
if (action instanceof org.apache.struts2.action.ApplicationAware) {
((org.apache.struts2.action.ApplicationAware) action).withApplication(context.getApplication());
}
@@ -167,7 +171,7 @@ public class ServletConfigInterceptor extends AbstractInterceptor implements Str
if (action instanceof SessionAware) {
((SessionAware) action).setSession(context.getSession());
}
if (action instanceof org.apache.struts2.action.SessionAware) {
((org.apache.struts2.action.SessionAware) action).withSession(context.getSession());
}
@@ -179,7 +183,7 @@ public class ServletConfigInterceptor extends AbstractInterceptor implements Str
if (action instanceof PrincipalAware) {
HttpServletRequest request = context.getServletRequest();
if(request != null) {
// We are in servtlet environment, so principal information resides in HttpServletRequest
// We are in servlet environment, so principal information resides in HttpServletRequest
((PrincipalAware) action).setPrincipalProxy(new ServletPrincipalProxy(request));
}
}
@@ -229,6 +229,37 @@ struts.ognl.logMissingProperties=false
### if the application generates a lot of different expressions
struts.ognl.enableExpressionCache=true
### Specify the OGNL expression cache factory and BeanInfo cache factory to use.
### Currently, the default implementations are used, but can be replaced with custom ones if desired.
struts.ognl.expressionCacheFactory=defaultOgnlExpressionCacheFactory
struts.ognl.beanInfoCacheFactory=defaultOgnlBeanInfoCacheFactory
### Specify a limit to the number of entries in the OGNL expressionCache.
### For the standard expressionCache mode, when the limit is exceeded the entire cache's
### content will be cleared (can help prevent memory leaks).
### For expressionCacheLRUMode true, the limit will ensure the cache does not exceed
### that size, dropping the oldest (least-recently-used) expressions to add new ones.
### NOTE: If not set, the default is 25000, which may be excessive.
# struts.ognl.expressionCacheMaxSize=1000
### Indicates if the OGNL expressionCache should use LRU mode.
### NOTE: When true, make sure to set the expressionCacheMaxSize to a reasonable value
### for your application. Otherwise the default limit will never (practically) be reached.
# struts.ognl.expressionCacheLRUMode=false
### Specify a limit to the number of entries in the OGNL beanInfoCache.
### For the standard beanInfoCache mode, when the limit is exceeded the entire cache's
### content will be cleared (can help prevent memory leaks).
### For beanInfoCacheLRUMode true, the limit will ensure the cache does not exceed
### that size, dropping the oldest (least-recently-used) expressions to add new ones.
### NOTE: If not set, the default is 25000, which may be excessive.
# struts.ognl.beanInfoCacheMaxSize=1000
### Indicates if the OGNL beanInfoCache should use LRU mode.
### NOTE: When true, make sure to set the beanInfoCacheMaxSize to a reasonable value
### for your application. Otherwise the default limit will never (practically) be reached.
# struts.ognl.beanInfoCacheLRUMode=false
### Indicates if Dispatcher should handle unexpected exceptions by calling sendError()
### or simply rethrow it as a ServletException to allow future processing by other frameworks like Spring Security
struts.handle.exception=true
@@ -243,4 +274,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 of both and more details is in WW-5016
struts.date.formatter=dateTimeFormatter
### END SNIPPET: complete_file
+7 -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,12 @@
<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"/>
<bean type="com.opensymphony.xwork2.ognl.OgnlCacheFactory" name="ognlExpressionCacheFactory" class="com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory" scope="singleton"/>
<bean type="com.opensymphony.xwork2.ognl.OgnlCacheFactory" name="ognlBeanInfoCacheFactory" class="com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory" scope="singleton"/>
<package name="struts-default" abstract="true">
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
@@ -35,6 +35,7 @@ import com.opensymphony.xwork2.util.Owner;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import java.beans.BeanInfo;
import ognl.InappropriateExpressionException;
import ognl.MethodFailedException;
import ognl.NoSuchPropertyException;
@@ -129,6 +130,61 @@ public class OgnlUtilTest extends XWorkTestCase {
assertSame(expr0, expr2);
}
public void testCacheEnabledMaxSize() throws OgnlException {
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setExpressionCacheMaxSize("1");
Object expr0 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
assertSame(expr0, expr2);
assertEquals("Expression cache size should be at its limit", 1, ognlUtil.expressionCacheSize());
// Next epxression cached should cause the cache to clear (exceeding maximum sized).
Object expr3 = ognlUtil.compile("test1");
assertEquals("Expression cache should be empty", 0, ognlUtil.expressionCacheSize());
Object expr4 = ognlUtil.compile("test1");
Object expr5 = ognlUtil.compile("test1");
assertEquals("Expression cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
assertNotSame("2nd test expression cache attempt will exceed size and force clear, but somehow they match ?", expr3, expr4);
assertSame(expr4, expr5);
// Next epxression cached should cause the cache to clear (exceeding maximum sized).
Object expr6 = ognlUtil.compile("test");
assertEquals("Expression cache should be empty", 0, ognlUtil.expressionCacheSize());
Object expr7 = ognlUtil.compile("test");
Object expr8 = ognlUtil.compile("test");
assertNotSame("2nd test expression cache attempt will exceed size and force clear, but somehow they match ?", expr6, expr7);
assertSame(expr7, expr8);
assertEquals("Expression LRU cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
assertNotSame("1st test expression identical after ejection from LRU cache ?", expr5, expr0);
}
public void testLRUCacheEnabled() throws OgnlException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setEnableExpressionCache("true");
Object expr0 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
assertSame(expr0, expr2);
}
public void testLRUCacheEnabledMaxSize() throws OgnlException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setEnableExpressionCache("true");
ognlUtil.setExpressionCacheMaxSize("1");
Object expr0 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
assertSame(expr0, expr2);
assertEquals("Expression LRU cache size should be at its limit", 1, ognlUtil.expressionCacheSize());
Object expr3 = ognlUtil.compile("test1");
Object expr4 = ognlUtil.compile("test1");
assertSame(expr3, expr4);
assertEquals("Expression LRU cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
Object expr5 = ognlUtil.compile("test");
Object expr6 = ognlUtil.compile("test");
assertSame(expr5, expr6);
assertEquals("Expression LRU cache size should still be at its limit", 1, ognlUtil.expressionCacheSize());
assertNotSame("1st test expression identical after ejection from LRU cache ?", expr5, expr0);
}
public void testExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws OgnlException {
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
@@ -149,6 +205,30 @@ public class OgnlUtilTest extends XWorkTestCase {
}
}
public void testExpressionIsLRUCachedIrrespectiveOfItsExecutionStatus() throws OgnlException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setContainer(container); // Must be explicitly set as the generated OgnlUtil instance has no container
ognlUtil.setEnableExpressionCache("true");
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
// Expression which executes with success
try {
ognlUtil.getValue("@com.opensymphony.xwork2.ognl.OgnlUtilTest@STATIC_FINAL_PUBLIC_ATTRIBUTE", context, foo);
assertEquals("Successfully executed expression must have been cached", ognlUtil.expressionCacheSize(), 1);
} catch (Exception ex) {
fail("Expression execution should have succeeded here. Exception: " + ex);
}
// Expression which executes with failure
try {
ognlUtil.getValue("@com.opensymphony.xwork2.ognl.OgnlUtilTest@STATIC_PRIVATE_ATTRIBUTE", context, foo);
fail("Expression execution should have failed here");
} catch (Exception ex) {
assertEquals("Expression with failed execution must have been cached nevertheless", ognlUtil.expressionCacheSize(), 2);
}
}
public void testMethodExpressionIsCachedIrrespectiveOfItsExecutionStatus() throws Exception {
Foo foo = new Foo();
OgnlContext context = (OgnlContext) ognlUtil.createDefaultContext(foo);
@@ -193,6 +273,32 @@ public class OgnlUtilTest extends XWorkTestCase {
assertTrue("Expression cache empty after usage ?", ognlUtil.expressionCacheSize() > 0);
}
public void testClearExpressionLRUCache() throws OgnlException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setEnableExpressionCache("true");
// Test that the expression cache is functioning as expected.
Object expr0 = ognlUtil.compile("test");
Object expr1 = ognlUtil.compile("test");
Object expr2 = ognlUtil.compile("test");
// Cache in effect, so expr0, expr1, expr2 should be the same.
assertSame(expr0, expr1);
assertSame(expr0, expr2);
assertTrue("Expression cache empty before clear ?", ognlUtil.expressionCacheSize() > 0);
// Clear the Epxression cache and confirm subsequent requests are new.
ognlUtil.clearExpressionCache();
assertEquals("Expression cache not empty after clear ?", 0, ognlUtil.expressionCacheSize());
Object expr3 = ognlUtil.compile("test");
Object expr4 = ognlUtil.compile("test");
Object expr5 = ognlUtil.compile("test");
// Cache cleared, expr3 should be a new instance.
assertNotSame(expr0, expr3);
// Cache still in effect, so expr3, expr4, expr5 should be the same.
assertSame(expr3, expr4);
assertSame(expr3, expr5);
assertTrue("Expression cache empty after usage ?", ognlUtil.expressionCacheSize() > 0);
}
public void testClearBeanInfoCache() throws IntrospectionException {
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
@@ -242,6 +348,79 @@ public class OgnlUtilTest extends XWorkTestCase {
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
}
public void testBeanInfoCache() throws IntrospectionException {
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
// Test that the BeanInfo cache is functioning as expected.
Object beanInfo1_1 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_2 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_3 = ognlUtil.getBeanInfo(testBean1);
// Cache in effect, so beanInfo1_1, beanInfo1_2, beanInfo1_3 should be the same.
assertSame(beanInfo1_1, beanInfo1_2);
assertSame(beanInfo1_1, beanInfo1_3);
Object beanInfo2_1 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_2 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_3 = ognlUtil.getBeanInfo(testBean2);
// Cache in effect, so beanInfo2_1, beanInfo2_2, beanInfo2_3 should be the same.
assertSame(beanInfo2_1, beanInfo2_2);
assertSame(beanInfo2_1, beanInfo2_3);
// BeanInfo for TestBean1 and TestBean2 should always be different.
assertNotSame(beanInfo1_1, beanInfo2_1);
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
}
public void testBeanInfoLRUCache() throws IntrospectionException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
// Test that the BeanInfo cache is functioning as expected.
Object beanInfo1_1 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_2 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_3 = ognlUtil.getBeanInfo(testBean1);
// Cache in effect, so beanInfo1_1, beanInfo1_2, beanInfo1_3 should be the same.
assertSame(beanInfo1_1, beanInfo1_2);
assertSame(beanInfo1_1, beanInfo1_3);
Object beanInfo2_1 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_2 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_3 = ognlUtil.getBeanInfo(testBean2);
// Cache in effect, so beanInfo2_1, beanInfo2_2, beanInfo2_3 should be the same.
assertSame(beanInfo2_1, beanInfo2_2);
assertSame(beanInfo2_1, beanInfo2_3);
// BeanInfo for TestBean1 and TestBean2 should always be different.
assertNotSame(beanInfo1_1, beanInfo2_1);
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
}
public void testBeanInfoLRUCacheLimits() throws IntrospectionException {
// Force usage of LRU cache factories for the OgnlUtil instance
this.ognlUtil = generateOgnlUtilInstanceWithDefaultLRUCacheFactories();
ognlUtil.setBeanInfoCacheMaxSize("1");
final TestBean1 testBean1 = new TestBean1();
final TestBean2 testBean2 = new TestBean2();
// Test that the BeanInfo cache is functioning as expected.
Object beanInfo1_1 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_2 = ognlUtil.getBeanInfo(testBean1);
Object beanInfo1_3 = ognlUtil.getBeanInfo(testBean1);
// Cache in effect, so beanInfo1_1, beanInfo1_2, beanInfo1_3 should be the same.
assertSame(beanInfo1_1, beanInfo1_2);
assertSame(beanInfo1_1, beanInfo1_3);
Object beanInfo2_1 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_2 = ognlUtil.getBeanInfo(testBean2);
Object beanInfo2_3 = ognlUtil.getBeanInfo(testBean2);
// Cache in effect, so beanInfo2_1, beanInfo2_2, beanInfo2_3 should be the same.
assertSame(beanInfo2_1, beanInfo2_2);
assertSame(beanInfo2_1, beanInfo2_3);
// BeanInfo for TestBean1 and TestBean2 should always be different.
assertNotSame(beanInfo1_1, beanInfo2_1);
assertTrue("BeanInfo cache empty after usage ?", ognlUtil.beanInfoCacheSize() > 0);
assertEquals("BeanInfo LRU cache size should be at its limit", 1, ognlUtil.beanInfoCacheSize());
// LRU cache should not contain TestBean1 beaninfo anymore. A new entry should exist in the cache.
Object beanInfo1_4 = ognlUtil.getBeanInfo(testBean1);
assertNotSame("BeanInfo dropped from LRU cache is the same as newly added ?", beanInfo1_1, beanInfo1_4);
ognlUtil.setBeanInfoCacheMaxSize(String.valueOf(Integer.MAX_VALUE));
}
public void testClearRuntimeCache() {
// Confirm that no exceptions or failures arise when calling the convenience global clear method.
OgnlUtil.clearRuntimeCache();
@@ -1134,6 +1313,20 @@ public class OgnlUtilTest extends XWorkTestCase {
internalTestOgnlUtilExclusionsImmutable(basicOgnlUtil);
}
public void testDefaultOgnlUtilExclusionsAlternateConstructor() {
OgnlUtil basicOgnlUtil = new OgnlUtil(null, null);
internalTestInitialEmptyOgnlUtilExclusions(basicOgnlUtil);
internalTestOgnlUtilExclusionsImmutable(basicOgnlUtil);
}
public void testDefaultOgnlUtilExclusionsAlternateConstructorPopulated() {
OgnlUtil basicOgnlUtil = new OgnlUtil(new DefaultOgnlExpressionCacheFactory<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo>());
internalTestInitialEmptyOgnlUtilExclusions(basicOgnlUtil);
internalTestOgnlUtilExclusionsImmutable(basicOgnlUtil);
}
public void testOgnlUtilExcludedAdditivity() {
Set<Class<?>> excludedClasses;
Set<Pattern> excludedPackageNamePatterns;
@@ -1497,6 +1690,34 @@ public class OgnlUtilTest extends XWorkTestCase {
}
}
public void testGetExcludedPackageNamesAlternateConstructor() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil(null, null);
util.setExcludedPackageNames("java.lang,java.awt");
assertEquals(util.getExcludedPackageNames().size(), 2);
try {
util.getExcludedPackageNames().clear();
} catch (Exception ex) {
assertTrue(ex instanceof UnsupportedOperationException);
} finally {
assertEquals(util.getExcludedPackageNames().size(), 2);
}
}
public void testGetExcludedPackageNamesAlternateConstructorPopulated() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil(new DefaultOgnlExpressionCacheFactory<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo>());
util.setExcludedPackageNames("java.lang,java.awt");
assertEquals(util.getExcludedPackageNames().size(), 2);
try {
util.getExcludedPackageNames().clear();
} catch (Exception ex) {
assertTrue(ex instanceof UnsupportedOperationException);
} finally {
assertEquals(util.getExcludedPackageNames().size(), 2);
}
}
public void testGetExcludedClasses() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil();
@@ -1511,6 +1732,34 @@ public class OgnlUtilTest extends XWorkTestCase {
}
}
public void testGetExcludedClassesAlternateConstructor() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil(null, null);
util.setExcludedClasses("java.lang.Runtime,java.lang.ProcessBuilder,java.net.URL");
assertEquals(util.getExcludedClasses().size(), 3);
try {
util.getExcludedClasses().clear();
} catch (Exception ex) {
assertTrue(ex instanceof UnsupportedOperationException);
} finally {
assertEquals(util.getExcludedClasses().size(), 3);
}
}
public void testGetExcludedClassesAlternateConstructorPopulated() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil(new DefaultOgnlExpressionCacheFactory<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo>());
util.setExcludedClasses("java.lang.Runtime,java.lang.ProcessBuilder,java.net.URL");
assertEquals(util.getExcludedClasses().size(), 3);
try {
util.getExcludedClasses().clear();
} catch (Exception ex) {
assertTrue(ex instanceof UnsupportedOperationException);
} finally {
assertEquals(util.getExcludedClasses().size(), 3);
}
}
public void testGetExcludedPackageNamePatterns() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil();
@@ -1525,6 +1774,132 @@ public class OgnlUtilTest extends XWorkTestCase {
}
}
public void testGetExcludedPackageNamePatternsAlternateConstructor() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil(null, null);
util.setExcludedPackageNamePatterns("java.lang.");
assertEquals(util.getExcludedPackageNamePatterns().size(), 1);
try {
util.getExcludedPackageNamePatterns().clear();
} catch (Exception ex) {
assertTrue(ex instanceof UnsupportedOperationException);
} finally {
assertEquals(util.getExcludedPackageNamePatterns().size(), 1);
}
}
public void testGetExcludedPackageNamePatternsAlternateConstructorPopulated() {
// Getter should return an immutable collection
OgnlUtil util = new OgnlUtil(new DefaultOgnlExpressionCacheFactory<String, Object>(), new DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo>());
util.setExcludedPackageNamePatterns("java.lang.");
assertEquals(util.getExcludedPackageNamePatterns().size(), 1);
try {
util.getExcludedPackageNamePatterns().clear();
} catch (Exception ex) {
assertTrue(ex instanceof UnsupportedOperationException);
} finally {
assertEquals(util.getExcludedPackageNamePatterns().size(), 1);
}
}
public void testOgnlUtilDefaultCacheClass() throws OgnlException {
OgnlDefaultCache<Integer, String> defaultCache = new OgnlDefaultCache<>(2, 16, 0.75f);
assertEquals("Initial evictionLimit did not match initial value", 2, defaultCache.getEvictionLimit());
defaultCache.setEvictionLimit(3);
assertEquals("Updated evictionLimit did not match updated value", 3, defaultCache.getEvictionLimit());
String lookupResult = defaultCache.get(Integer.valueOf(0));
assertNull("Lookup of empty cache returned non-null value ?", lookupResult);
defaultCache.put(Integer.valueOf(0), "Zero");
lookupResult = defaultCache.get(Integer.valueOf(0));
assertEquals("Retrieved value does not match put value ?", "Zero", lookupResult);
defaultCache.put(Integer.valueOf(1), "One");
defaultCache.put(Integer.valueOf(2), "Two");
assertEquals("Default cache not size evictionlimit after adding three values ?", defaultCache.getEvictionLimit(), defaultCache.size());
lookupResult = defaultCache.get(Integer.valueOf(2));
assertEquals("Retrieved value does not match put value ?", "Two", lookupResult);
defaultCache.put(Integer.valueOf(3), "Three");
assertEquals("Default cache not size zero after an add that exceeded the evection limit ?", 0, defaultCache.size());
lookupResult = defaultCache.get(Integer.valueOf(0));
assertNull("Lookup of value 0 (should have been evicted with everything) returned non-null value ?", lookupResult);
lookupResult = defaultCache.get(Integer.valueOf(3));
assertNull("Lookup of value 3 (should have been evicted with everything) returned non-null value ?", lookupResult);
defaultCache.putIfAbsent(Integer.valueOf(2), "Two");
lookupResult = defaultCache.get(Integer.valueOf(2));
assertEquals("Retrieved value does not match put value ?", "Two", lookupResult);
defaultCache.clear();
assertEquals("Default cache not empty after clear ?", 0, defaultCache.size());
}
public void testOgnlUtilLRUCacheClass() throws OgnlException {
OgnlLRUCache<Integer, String> lruCache = new OgnlLRUCache<>(2, 16, 0.75f);
assertEquals("Initial evictionLimit did not match initial value", 2, lruCache.getEvictionLimit());
lruCache.setEvictionLimit(3);
assertEquals("Updated evictionLimit did not match updated value", 3, lruCache.getEvictionLimit());
String lookupResult = lruCache.get(Integer.valueOf(0));
assertNull("Lookup of empty cache returned non-null value ?", lookupResult);
lruCache.put(Integer.valueOf(0), "Zero");
lookupResult = lruCache.get(Integer.valueOf(0));
assertEquals("Retrieved value does not match put value ?", "Zero", lookupResult);
lruCache.put(Integer.valueOf(1), "One");
lruCache.put(Integer.valueOf(2), "Two");
assertEquals("LRU cache not size evictionlimit after adding three values ?", lruCache.getEvictionLimit(), lruCache.size());
lookupResult = lruCache.get(Integer.valueOf(2));
assertEquals("Retrieved value does not match put value ?", "Two", lookupResult);
lruCache.put(Integer.valueOf(3), "Three");
assertEquals("LRU cache not size evictionlimit after adding values ?", lruCache.getEvictionLimit(), lruCache.size());
lookupResult = lruCache.get(Integer.valueOf(0));
assertNull("Lookup of value 0 (should have dropped off LRU cache) returned non-null value ?", lookupResult);
lruCache.putIfAbsent(Integer.valueOf(2), "Two");
lookupResult = lruCache.get(Integer.valueOf(2));
assertEquals("Retrieved value does not match put value ?", "Two", lookupResult);
lruCache.clear();
assertEquals("LRU cache not empty after clear ?", 0, lruCache.size());
}
/**
* Unit test primarily for code coverage
*/
public void testOgnlDefaultCacheFactoryCoverage() {
OgnlCache<String, Object> ognlCache;
DefaultOgnlCacheFactory defaultOgnlCacheFactory = new DefaultOgnlCacheFactory<String, Object>();
// Normal cache
defaultOgnlCacheFactory.setCacheMaxSize("12");
defaultOgnlCacheFactory.setUseLRUCache("false");
ognlCache = defaultOgnlCacheFactory.buildOgnlCache();
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 12, ognlCache.getEvictionLimit() );
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(6, 6, 0.75f, false);
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 6, ognlCache.getEvictionLimit() );
// LRU cache
defaultOgnlCacheFactory.setCacheMaxSize("30");
defaultOgnlCacheFactory.setUseLRUCache("true");
ognlCache = defaultOgnlCacheFactory.buildOgnlCache();
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 30, ognlCache.getEvictionLimit() );
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(15, 15, 0.75f, false);
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 15, ognlCache.getEvictionLimit() );
}
/**
* Generate a new OgnlUtil instance (not configured by the {@link ContainerBuilder}) that can be used for
* basic tests, with its Expression and BeanInfo factories set to LRU mode.
*
* @return OgnlUtil instance with LRU enabled Expression and BeanInfo factories
*/
private OgnlUtil generateOgnlUtilInstanceWithDefaultLRUCacheFactories() {
final OgnlUtil result;
final DefaultOgnlExpressionCacheFactory<String, Object> expressionFactory = new DefaultOgnlExpressionCacheFactory<>();
final DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo> beanInfoFactory = new DefaultOgnlBeanInfoCacheFactory<>();
expressionFactory.setUseLRUCache("true");
expressionFactory.setCacheMaxSize("25");
beanInfoFactory.setUseLRUCache("true");
beanInfoFactory.setCacheMaxSize("25");
result = new OgnlUtil(expressionFactory, beanInfoFactory);
return result;
}
private void reloadTestContainerConfiguration(boolean devMode, boolean allowStaticMethod) {
loadConfigurationProviders(new StubConfigurationProvider() {
@Override
@@ -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();
}
}
@@ -19,35 +19,33 @@
package org.apache.struts2.views.jsp.ui;
import com.opensymphony.xwork2.ActionContext;
import org.apache.struts2.TestAction;
import org.apache.struts2.components.Component;
import org.apache.struts2.components.DateTextField;
import org.apache.struts2.views.jsp.AbstractTagTest;
import org.apache.struts2.views.jsp.DateTag;
import javax.servlet.jsp.JspException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.apache.struts2.components.Component;
import org.apache.struts2.components.DateTextField;
/**
* Unit test for {@link org.apache.struts2.components.Date}.
*
*/
public class DateTagTest extends AbstractTagTest {
private DateTag tag;
public void testCustomFormat() throws Exception {
public void testCustomFormatForDateTime() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = new Date();
String formatted = new SimpleDateFormat(format).format(now);
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setName("myDate");
@@ -62,13 +60,55 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatForLong() throws Exception {
String format = "yyyy/MM/dd";
long now = new Date().getTime();
String formatted = DateTimeFormatter.ofPattern(format).format(Instant.ofEpochMilli(now).atZone(ZoneId.systemDefault()));
context.put("myDate", now);
tag.setName("myDate");
tag.setNice(false);
tag.setFormat(format);
tag.doStartTag();
tag.doEndTag();
assertEquals(formatted, writer.toString());
// Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
DateTag freshTag = new DateTag();
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatForDate() throws Exception {
String format = "yyyy/MM/dd";
LocalDate now = LocalDate.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setName("myDate");
tag.setNice(false);
tag.setFormat(format);
tag.doStartTag();
tag.doEndTag();
assertEquals(formatted, writer.toString());
// Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
DateTag freshTag = new DateTag();
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormat_clearTagStateSet() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = new Date();
String formatted = new SimpleDateFormat(format).format(now);
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
@@ -86,13 +126,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomGlobalFormatFormat() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = new Date();
String formatted = new SimpleDateFormat(format).format(now);
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
((TestAction) action).setText(org.apache.struts2.components.Date.DATETAG_PROPERTY, format);
@@ -106,10 +146,8 @@ public class DateTagTest extends AbstractTagTest {
public void testCustomFormatWithTimezone() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+1")).getTime();
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setTimeZone(TimeZone.getTimeZone("GMT+1"));
String formatted = sdf.format(now);
LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+1"));
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setName("myDate");
@@ -126,15 +164,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatWithTimezone_clearTagStateSet() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+1")).getTime();
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setTimeZone(TimeZone.getTimeZone("GMT+1"));
String formatted = sdf.format(now);
LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+1"));
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
@@ -153,15 +189,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatWithTimezoneAsExpression() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+2")).getTime();
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setTimeZone(TimeZone.getTimeZone("GMT+2"));
String formatted = sdf.format(now);
LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+2"));
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
context.put("myTimezone", "GMT+2");
@@ -178,15 +212,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatWithTimezoneAsExpression_clearTagStateSet() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = Calendar.getInstance(TimeZone.getTimeZone("GMT+2")).getTime();
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setTimeZone(TimeZone.getTimeZone("GMT+2"));
String formatted = sdf.format(now);
LocalDateTime now = LocalDateTime.now(ZoneId.of("GMT+2"));
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
context.put("myTimezone", "GMT+2");
@@ -206,13 +238,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatCalendar() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Calendar calendar = Calendar.getInstance();
String formatted = new SimpleDateFormat(format).format(calendar.getTime());
String formatted = DateTimeFormatter.ofPattern(format).format(calendar.toInstant().atZone(ZoneId.systemDefault()));
context.put("myDate", calendar);
tag.setName("myDate");
@@ -227,13 +259,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatCalendar_clearTagStateSet() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Calendar calendar = Calendar.getInstance();
String formatted = new SimpleDateFormat(format).format(calendar.getTime());
String formatted = DateTimeFormatter.ofPattern(format).format(calendar.toInstant().atZone(ZoneId.systemDefault()));
context.put("myDate", calendar);
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
@@ -251,13 +283,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatLong() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date date = new Date();
String formatted = new SimpleDateFormat(format).format(date);
String formatted = DateTimeFormatter.ofPattern(format).format(date.toInstant().atZone(ZoneId.systemDefault()));
// long
context.put("myDate", date.getTime());
@@ -273,13 +305,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatLong_clearTagStateSet() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date date = new Date();
String formatted = new SimpleDateFormat(format).format(date);
String formatted = DateTimeFormatter.ofPattern(format).format(date.toInstant().atZone(ZoneId.systemDefault()));
// long
context.put("myDate", date.getTime());
@@ -298,7 +330,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatLocalDateTime() throws Exception {
@@ -332,7 +364,7 @@ public class DateTagTest extends AbstractTagTest {
public void testDefaultFormat() throws Exception {
Date now = new Date();
String formatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
ActionContext.getContext().getLocale()).format(now);
ActionContext.getContext().getLocale()).format(now);
context.put("myDate", now);
tag.setName("myDate");
@@ -346,13 +378,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testDefaultFormat_clearTagStateSet() throws Exception {
Date now = new Date();
String formatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM,
ActionContext.getContext().getLocale()).format(now);
ActionContext.getContext().getLocale()).format(now);
context.put("myDate", now);
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
@@ -369,13 +401,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatAndComponent() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = new Date();
String formatted = new SimpleDateFormat(format).format(now);
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setName("myDate");
@@ -388,7 +420,7 @@ public class DateTagTest extends AbstractTagTest {
org.apache.struts2.components.Date component = (org.apache.struts2.components.Date) tag.getComponent();
assertEquals("myDate", component.getName());
assertEquals(format, component.getFormat());
assertEquals(false, component.isNice());
assertFalse(component.isNice());
tag.doEndTag();
@@ -399,13 +431,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testCustomFormatAndComponent_clearTagStateSet() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = new Date();
String formatted = new SimpleDateFormat(format).format(now);
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
@@ -420,7 +452,7 @@ public class DateTagTest extends AbstractTagTest {
org.apache.struts2.components.Date component = (org.apache.struts2.components.Date) tag.getComponent();
assertEquals("myDate", component.getName());
assertEquals(format, component.getFormat());
assertEquals(false, component.isNice());
assertFalse(component.isNice());
tag.doEndTag();
@@ -432,13 +464,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testSetId() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = new Date();
String formatted = new SimpleDateFormat(format).format(now);
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setName("myDate");
@@ -454,13 +486,13 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testSetId_clearTagStateSet() throws Exception {
String format = "yyyy/MM/dd hh:mm:ss";
Date now = new Date();
String formatted = new SimpleDateFormat(format).format(now);
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format).format(now);
context.put("myDate", now);
tag.setPerformClearTagStateForTagPoolingServers(true); // Explicitly request tag state clearing.
@@ -479,7 +511,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureNiceHour() throws Exception {
@@ -501,7 +533,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureNiceHour_clearTagStateSet() throws Exception {
@@ -526,7 +558,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testPastNiceHour() throws Exception {
@@ -548,7 +580,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testPastNiceHour_clearTagStateSet() throws Exception {
@@ -573,7 +605,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureNiceHourMinSec() throws Exception {
@@ -596,7 +628,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureNiceHourMinSec_clearTagStateSet() throws Exception {
@@ -622,7 +654,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testPastNiceHourMin() throws Exception {
@@ -645,7 +677,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testPastNiceHourMin_clearTagStateSet() throws Exception {
@@ -671,7 +703,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureLessOneMin() throws Exception {
@@ -693,7 +725,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureLessOneMin_clearTagStateSet() throws Exception {
@@ -718,7 +750,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureLessOneHour() throws Exception {
@@ -740,7 +772,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureLessOneHour_clearTagStateSet() throws Exception {
@@ -765,7 +797,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureLessOneYear() throws Exception {
@@ -787,7 +819,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureLessOneYear_clearTagStateSet() throws Exception {
@@ -812,7 +844,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureTwoYears() throws Exception {
@@ -838,7 +870,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testFutureTwoYears_clearTagStateSet() throws Exception {
@@ -867,7 +899,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testNoDateObjectInContext() throws Exception {
@@ -884,7 +916,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testNoDateObjectInContext_clearTagStateSet() throws Exception {
@@ -904,7 +936,7 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
/**
@@ -912,10 +944,8 @@ public class DateTagTest extends AbstractTagTest {
* since that tag does not have its own unit tests, and it also appears to be
* a broken tag. The code coverage tests can be moved if the tag is fixed, or
* removed if the tag is dropped.
*
* @throws Exception
*/
public void testDateTextFieldTag_artificialCoverageTest() throws Exception {
public void testDateTextFieldTag_artificialCoverageTest() throws JspException {
final String format = "yyyy/MM/dd hh:mm:ss";
DateTextFieldTag dateTextFieldTag = createDateTextFieldTag();
dateTextFieldTag.setFormat(format);
@@ -939,20 +969,40 @@ public class DateTagTest extends AbstractTagTest {
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(dateTextFieldTag, freshTag));
strutsBodyTagsAreReflectionEqual(dateTextFieldTag, freshTag));
}
public void testNewJava8Format() throws Exception {
String format = "EEEE MMMM dd, hh:mm a";
LocalDateTime now = LocalDateTime.now();
String formatted = DateTimeFormatter.ofPattern(format, ActionContext.getContext().getLocale()).format(now);
context.put("myDate", now);
tag.setName("myDate");
tag.setNice(false);
tag.setFormat(format);
tag.doStartTag();
tag.doEndTag();
assertEquals(formatted, writer.toString());
// Basic sanity check of clearTagStateForTagPoolingServers() behaviour for Struts Tags after doEndTag().
DateTag freshTag = new DateTag();
freshTag.setPageContext(pageContext);
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
/**
* Utility method to create a new {@link DateTextFieldTag} instance for code coverage tests.
*
* <p>
* Note: There is no datetextfield.ftl template for the tag, so it does not appear that it can
* actually be used in practice. We can perform basic coverage tests from within this
* unit test class until the {@link DateTextFieldTag} is fixed or removed.
*
* actually be used in practice. We can perform basic coverage tests from within this
* unit test class until the {@link DateTextFieldTag} is fixed or removed.
*
* @return a basic {@link DateTextFieldTag} instance
* @throws Exception
*/
private DateTextFieldTag createDateTextFieldTag() throws Exception {
private DateTextFieldTag createDateTextFieldTag() {
DateTextFieldTag tag = new DateTextFieldTag();
tag.setPageContext(pageContext);
tag.setName("myDate");
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-async-plugin</artifactId>
+13 -7
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -44,11 +44,6 @@
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
@@ -60,6 +55,17 @@
<artifactId>javax.el</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<!-- this library is excluded in the parent pom as it clashes with Easymock dependencies -->
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>3.2</version>
</dependency>
<!--
The Java EE API modules listed below are all marked @Deprecated(forRemoval=true), because they are scheduled
@@ -94,4 +100,4 @@
</dependencies>
</project>
</project>
@@ -20,17 +20,20 @@
*/
package org.apache.struts.beanvalidation.constraints.impl;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts.beanvalidation.constraints.FieldMatch;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private static final Logger LOG = LogManager.getLogger(FieldMatchValidator.class);
private String firstFieldName;
private String secondFieldName;
@@ -41,13 +44,23 @@ public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Obje
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
try {
final Object firstObj = PropertyUtils.getProperty(value, this.firstFieldName);
final Object secondObj = PropertyUtils.getProperty(value, this.secondFieldName);
final Object firstObj = readPropertyValue(value, this.firstFieldName);
final Object secondObj = readPropertyValue(value, this.secondFieldName);
return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
} catch (final Exception ex) {
LOG.info("Error while getting values from object", ex);
return false;
}
}
private Object readPropertyValue(Object bean, String propertyName) throws Exception {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
if (propertyName.equals(descriptor.getName())) {
return descriptor.getReadMethod().invoke(bean);
}
}
return null;
}
}
@@ -0,0 +1,84 @@
/*
* 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.struts.beanvalidation.constraints.impl;
import org.apache.struts.beanvalidation.constraints.FieldMatch;
import org.junit.Test;
import org.mockito.Mockito;
import javax.validation.ConstraintValidatorContext;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class FieldMatchValidatorTest {
@Test
public void matchingFields() {
// given
FieldMatchValidator validator = new FieldMatchValidator();
validator.initialize(FieldMatchTestBean.class.getAnnotation(FieldMatch.class));
ConstraintValidatorContext context = Mockito.mock(ConstraintValidatorContext.class);
// when
FieldMatchTestBean bean = new FieldMatchTestBean("12345678", "12345678");
boolean valid = validator.isValid(bean, context);
// then
assertTrue(valid);
}
@Test
public void notMatchingFields() {
// given
FieldMatchValidator validator = new FieldMatchValidator();
validator.initialize(FieldMatchTestBean.class.getAnnotation(FieldMatch.class));
ConstraintValidatorContext context = Mockito.mock(ConstraintValidatorContext.class);
// when
FieldMatchTestBean bean = new FieldMatchTestBean("12345678", "87654321");
boolean valid = validator.isValid(bean, context);
// then
assertFalse(valid);
}
@FieldMatch(first = "password", second = "repeatPassword")
public static class FieldMatchTestBean {
String password;
String repeatPassword;
public FieldMatchTestBean(String password, String repeatPassword) {
this.password = password;
this.repeatPassword = repeatPassword;
}
public String getPassword() {
return password;
}
public String getRepeatPassword() {
return repeatPassword;
}
}
}
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-cdi-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-config-browser-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-convention-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-dwr-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-embeddedjsp-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-gxp-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-jasperreports-plugin</artifactId>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-javatemplates-plugin</artifactId>
@@ -28,7 +28,7 @@ import java.util.LinkedHashMap;
*/
public class Attributes extends LinkedHashMap<String, String> {
private static final long serialVersionUID = 4103241472140545630L;
private static final long serialVersionUID = 4103241472140545630L;
public Attributes add(String key, String value) {
return add(key, value, true);
@@ -74,15 +74,17 @@ public class Attributes extends LinkedHashMap<String, String> {
* @return this
*/
public Attributes addIfTrue(String attrName, Object paramValue) {
if (paramValue != null) {
if ((paramValue instanceof Boolean && ((Boolean) paramValue).booleanValue()) ||
(Boolean.valueOf(paramValue.toString()).booleanValue())) {
put(attrName, attrName);
}
if (paramValue != null && isTrue(paramValue)) {
put(attrName, attrName);
}
return this;
}
private boolean isTrue(Object paramValue) {
return (paramValue instanceof Boolean && (Boolean) paramValue)
|| (Boolean.parseBoolean(paramValue.toString()));
}
/**
* Add a key/value pair to the attributes, if the value is null, it will be set as an empty string.
* Value is html encoded.
@@ -37,12 +37,14 @@ public class AnchorHandler extends AbstractTagHandler implements TagGenerator {
Attributes attrs = new Attributes();
attrs.addIfExists("name", params.get("name"))
.addIfExists("id", params.get("id"))
.addIfExists("class", params.get("cssClass"))
.addIfExists("style", params.get("cssStyle"))
.addIfExists("href", params.get("href"), false)
.addIfExists("title", params.get("title"))
.addIfExists("tabindex", params.get("tabindex"));
.addIfExists("id", params.get("id"))
.addIfExists("class", params.get("cssClass"))
.addIfExists("style", params.get("cssStyle"))
.addIfExists("href", params.get("href"), false)
.addIfTrue("disabled", params.get("disabled"))
.addIfExists("title", params.get("title"))
.addIfExists("tabindex", params.get("tabindex"));
start("a", attrs);
String body = (String) params.get("body");
Boolean escapeHtmlBody = (Boolean) params.get("escapeHtmlBody");
@@ -18,13 +18,14 @@
*/
package org.apache.struts2.views.java.simple;
import org.apache.struts2.views.java.Attributes;
import org.apache.struts2.views.java.TagGenerator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.struts2.views.java.Attributes;
import org.apache.struts2.views.java.TagGenerator;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
public class CheckboxHandler extends AbstractTagHandler implements TagGenerator {
public void generate() throws IOException {
@@ -35,29 +36,33 @@ public class CheckboxHandler extends AbstractTagHandler implements TagGenerator
String id = (String) params.get("id");
String name = (String) params.get("name");
Object disabled = params.get("disabled");
boolean submitUnchecked = Boolean.parseBoolean(Objects.toString(params.get("submitUnchecked"), "false"));
attrs.add("type", "checkbox")
.add("name", name)
.add("value", fieldValue)
.addIfTrue("checked", params.get("nameValue"))
.addIfTrue("readonly", params.get("readonly"))
.addIfTrue("disabled", disabled)
.addIfExists("tabindex", params.get("tabindex"))
.addIfExists("id", id)
.addIfExists("class", params.get("cssClass"))
.addIfExists("style", params.get("cssStyle"))
.addIfExists("title", params.get("title"));
.add("name", name)
.add("value", fieldValue)
.addIfTrue("checked", params.get("nameValue"))
.addIfTrue("readonly", params.get("readonly"))
.addIfTrue("disabled", disabled)
.addIfExists("tabindex", params.get("tabindex"))
.addIfExists("id", id)
.addIfExists("class", params.get("cssClass"))
.addIfExists("style", params.get("cssStyle"))
.addIfExists("title", params.get("title"));
start("input", attrs);
end("input");
//hidden input
attrs = new Attributes();
attrs.add("type", "hidden")
if (submitUnchecked) {
//hidden input
attrs = new Attributes();
attrs.add("type", "hidden")
.add("id", "__checkbox_" + StringUtils.defaultString(StringEscapeUtils.escapeHtml4(id)))
.add("name", "__checkbox_" + StringUtils.defaultString(StringEscapeUtils.escapeHtml4(name)))
.add("value", "__checkbox_" + StringUtils.defaultString(StringEscapeUtils.escapeHtml4(fieldValue)))
.addIfTrue("disabled", disabled);
start("input", attrs);
end("input");
start("input", attrs);
end("input");
}
}
}
@@ -40,9 +40,13 @@ public class LinkHandler extends AbstractTagHandler implements TagGenerator {
.addIfExists("referrerpolicy", params.get("referrerpolicy"))
.addIfExists("type", params.get("type"))
.addIfExists("as", params.get("as"))
.addIfExists("disabled", params.get("disabled"))
.addIfExists("title", params.get("title"));
// see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-disabled
if ("stylesheet".equals(params.get("rel"))) {
attrs.addIfTrue("disabled", params.get("disabled"));
}
start("link", attrs);
end("link");
}
@@ -18,9 +18,9 @@
*/
package org.apache.struts2.views.java.simple;
import org.apache.struts2.views.java.TagGenerator;
import org.apache.struts2.views.java.Attributes;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.views.java.Attributes;
import org.apache.struts2.views.java.TagGenerator;
import java.io.IOException;
import java.util.Map;
@@ -34,12 +34,13 @@ public class ResetHandler extends AbstractTagHandler implements TagGenerator {
boolean isButton = "button".equals(params.get("type"));
attrs.addDefaultToEmpty("name", params.get("name"))
.add("type", "reset")
.addIfExists("value", params.get("nameValue"))
.addIfExists("tabindex", params.get("tabindex"))
.addIfExists("id", params.get("id"))
.addIfExists("class", params.get("cssClass"))
.addIfExists("style", params.get("cssStyle"));
.add("type", "reset")
.addIfExists("value", params.get("nameValue"))
.addIfTrue("disabled", params.get("disabled"))
.addIfExists("tabindex", params.get("tabindex"))
.addIfExists("id", params.get("id"))
.addIfExists("class", params.get("cssClass"))
.addIfExists("style", params.get("cssStyle"));
if (!isButton)
attrs.addIfExists("title", params.get("title"));
@@ -43,7 +43,7 @@ public class AnchorTest extends AbstractTest {
theme.renderTag(getTagName(), context);
theme.renderTag(getTagName() + "-close", context);
String output = writer.getBuffer().toString();
String expected = s("<a name='name_' id='id_' class='class' style='style' href='http://sometest.com?ab=10' title='title' tabindex='1'></a>");
String expected = s("<a name='name_' id='id_' class='class' style='style' href='http://sometest.com?ab=10' disabled='disabled' title='title' tabindex='1'></a>");
assertEquals(expected, output);
}
@@ -29,7 +29,7 @@ public class CheckboxTest extends AbstractCommonAttributesTest {
public void testRenderCheckbox() {
tag.setName("name_");
tag.setDisabled("true");
tag.setDisabled("false");
tag.setTabindex("1");
tag.setId("id_");
tag.setCssClass("class");
@@ -41,20 +41,38 @@ public class CheckboxTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input type='checkbox' name='name_' value='xyz' tabindex='1' id='id_' class='class' style='style' title='title'></input><input type='hidden' id='__checkbox_id_' name='__checkbox_name_' value='__checkbox_xyz'></input>");
String expected = s("<input type='checkbox' name='name_' value='xyz' tabindex='1' id='id_' class='class' style='style' title='title'></input>");
assertEquals(expected, output);
}
public void testRenderUncheckCheckbox() {
tag.setName("name_");
tag.setDisabled("true");
tag.setTabindex("1");
tag.setId("id_");
tag.setCssClass("class");
tag.setCssStyle("style");
tag.setTitle("title");
tag.setFieldValue("xyz");
tag.setSubmitUnchecked("true");
tag.evaluateParams();
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input type='checkbox' name='name_' value='xyz' disabled='disabled' tabindex='1' id='id_' class='class' style='style' title='title'></input><input type='hidden' id='__checkbox_id_' name='__checkbox_name_' value='__checkbox_xyz' disabled='disabled'></input>");
assertEquals(expected, output);
}
public void testRenderCheckboxWithNameValue() {
tag.setName("name_");
tag.setValue("%{someValue}");
tag.setDisabled("true");
tag.evaluateParams();
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input type='checkbox' name='name_' value='true' checked='checked' id='name_'></input><input type='hidden' id='__checkbox_name_' name='__checkbox_name_' value='__checkbox_true'></input>");
String expected = s("<input type='checkbox' name='name_' value='true' checked='checked' id='name_'></input>");
assertEquals(expected, output);
}
@@ -43,7 +43,7 @@ public class FileTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input name='name' type='file' size='10' value='val1' accept='accept_' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
String expected = s("<input name='name' type='file' size='10' value='val1' disabled='disabled' accept='accept_' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
assertEquals(expected, output);
}
@@ -41,7 +41,7 @@ public class HiddenTest extends AbstractTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input name='name' type='hidden' value='val1' id='id1' class='class1' style='style1'></input>");
String expected = s("<input name='name' type='hidden' value='val1' disabled='disabled' id='id1' class='class1' style='style1'></input>");
assertEquals(expected, output);
}
@@ -31,7 +31,7 @@ public class LinkTest extends AbstractTest{
private static final String NONCE_VAL = "r4andom";
public void testRenderScriptTag() {
public void testRenderLinkTag() {
tag.setHref("testhref");
tag.setHreflang("test");
tag.setRel("module");
@@ -41,27 +41,60 @@ public class LinkTest extends AbstractTest{
tag.setCrossorigin("same-origin");
tag.setType("anonymous");
tag.setAs("test");
tag.setDisabled("disabled_");
tag.setDisabled("true");
tag.setTitle("test");
tag.evaluateParams();
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String s = writer.getBuffer().toString();
String output = writer.getBuffer().toString();
assertTrue("Incorrect href attribute for link tag", s.contains("href=\"testhref\""));
assertTrue("Incorrect hreflang attribute for link tag", s.contains("hreflang=\"test\""));
assertTrue("Incorrect rel attribute for link tag", s.contains("rel=\"module\""));
assertTrue("Incorrect media attribute for link tag", s.contains("media=\"foo\""));
assertTrue("Incorrect referrerpolicy attribute for link tag", s.contains("referrerpolicy=\"test\""));
assertTrue("Incorrect sizes attribute for link tag", s.contains("sizes=\"foo\""));
assertTrue("Incorrect crossorigin attribute for link tag", s.contains("crossorigin=\"same-origin\""));
assertTrue("Incorrect type attribute for link tag", s.contains("type=\"anonymous\""));
assertTrue("Incorrect as attribute for link tag", s.contains("as=\"test\""));
assertTrue("Non-existent disabled attribute for link tag", s.contains("disabled=\"disabled_\""));
assertTrue("Incorrect title attribute for link tag", s.contains("title=\"test\""));
assertTrue("Incorrect nonce attribute for link tag", s.contains("nonce=\"" + NONCE_VAL+"\""));
assertTrue("Incorrect href attribute for link tag", output.contains(s("href='testhref'")));
assertTrue("Incorrect hreflang attribute for link tag", output.contains(s("hreflang='test'")));
assertTrue("Incorrect rel attribute for link tag", output.contains(s("rel='module'")));
assertTrue("Incorrect media attribute for link tag", output.contains(s("media='foo'")));
assertTrue("Incorrect referrerpolicy attribute for link tag", output.contains(s("referrerpolicy='test'")));
assertTrue("Incorrect sizes attribute for link tag", output.contains(s("sizes='foo'")));
assertTrue("Incorrect crossorigin attribute for link tag", output.contains(s("crossorigin='same-origin'")));
assertTrue("Incorrect type attribute for link tag", output.contains(s("type='anonymous'")));
assertTrue("Incorrect as attribute for link tag", output.contains(s("as='test'")));
assertFalse("Non-existent disabled attribute for link tag", output.contains(s("disabled='disabled'")));
assertTrue("Incorrect title attribute for link tag", output.contains(s("title='test'")));
assertTrue("Incorrect nonce attribute for link tag", output.contains(s("nonce='" + NONCE_VAL+"'")));
}
public void testRenderLinkTagAsStylesheet() {
tag.setHref("testhref");
tag.setHreflang("test");
tag.setRel("stylesheet");
tag.setMedia("foo");
tag.setReferrerpolicy("test");
tag.setSizes("foo");
tag.setCrossorigin("same-origin");
tag.setType("anonymous");
tag.setAs("test");
tag.setDisabled("true");
tag.setTitle("test");
tag.evaluateParams();
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
assertTrue("Incorrect href attribute for link tag", output.contains(s("href='testhref'")));
assertTrue("Incorrect hreflang attribute for link tag", output.contains(s("hreflang='test'")));
assertTrue("Incorrect rel attribute for link tag", output.contains(s("rel='stylesheet'")));
assertTrue("Incorrect media attribute for link tag", output.contains(s("media='foo'")));
assertTrue("Incorrect referrerpolicy attribute for link tag", output.contains(s("referrerpolicy='test'")));
assertTrue("Incorrect sizes attribute for link tag", output.contains(s("sizes='foo'")));
assertTrue("Incorrect crossorigin attribute for link tag", output.contains(s("crossorigin='same-origin'")));
assertTrue("Incorrect type attribute for link tag", output.contains(s("type='anonymous'")));
assertTrue("Incorrect as attribute for link tag", output.contains(s("as='test'")));
assertTrue("Incorrect disabled attribute for link tag", output.contains(s("disabled='disabled'")));
assertTrue("Incorrect title attribute for link tag", output.contains(s("title='test'")));
assertTrue("Incorrect nonce attribute for link tag", output.contains(s("nonce='" + NONCE_VAL+"'")));
}
@Override
protected UIBean getUIBean() throws Exception {
return tag;
@@ -24,11 +24,10 @@ import org.apache.struts2.components.Password;
import org.apache.struts2.components.UIBean;
public class PasswordTest extends AbstractCommonAttributesTest {
private Password tag;
private boolean showPassword;
public void testRenderPassword() throws Exception {
this.showPassword = false;
super.setUp();
this.tag = new Password(stack, request, response);
@@ -47,12 +46,11 @@ public class PasswordTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input name='name' type='password' size='10' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
String expected = s("<input name='name' type='password' size='10' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
assertEquals(expected, output);
}
public void testRenderPasswordShowIt() throws Exception {
this.showPassword = true;
super.setUp();
this.tag = new Password(stack, request, response);
@@ -71,7 +69,7 @@ public class PasswordTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input value='val1' name='name' type='password' size='10' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
String expected = s("<input value='val1' name='name' type='password' size='10' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
assertEquals(expected, output);
}
@@ -45,7 +45,7 @@ public class SelectTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<select name='name_' size='10' tabindex='1' id='id_' class='class' style='style' title='title'></select>");
String expected = s("<select name='name_' size='10' disabled='disabled' tabindex='1' id='id_' class='class' style='style' title='title'></select>");
assertEquals(expected, output);
}
@@ -47,7 +47,7 @@ public class SubmitTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName() + "-close", context);
String output = writer.getBuffer().toString();
String expected = s("<button name='name' type='submit' value='val1' tabindex='1' id='id1' class='class1' style='style1'><span>hey hey hey, here I go now</span></button>");
String expected = s("<button name='name' type='submit' value='val1' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1'><span>hey hey hey, here I go now</span></button>");
assertEquals(expected, output);
}
@@ -69,7 +69,7 @@ public class SubmitTest extends AbstractCommonAttributesTest {
theme.renderTag(getTagName(), context);
theme.renderTag(getTagName() + "-close", context);
String output = writer.getBuffer().toString();
String expected = s("<button name='name' type='submit' value='val1' tabindex='1' id='id1' class='class1' style='style1'>Just as soon as I belong, than its time I disappear</button>");
String expected = s("<button name='name' type='submit' value='val1' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1'>Just as soon as I belong, than its time I disappear</button>");
assertEquals(expected, output);
}
@@ -91,7 +91,7 @@ public class SubmitTest extends AbstractCommonAttributesTest {
theme.renderTag(getTagName(), context);
theme.renderTag(getTagName() + "-close", context);
String output = writer.getBuffer().toString();
String expected = s("<input name='name' type='submit' value='val1' tabindex='1' id='id1' class='class1' style='style1'></input>");
String expected = s("<input name='name' type='submit' value='val1' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1'></input>");
assertEquals(expected, output);
}
@@ -112,7 +112,7 @@ public class SubmitTest extends AbstractCommonAttributesTest {
theme.renderTag(getTagName(), context);
theme.renderTag(getTagName() + "-close", context);
String output = writer.getBuffer().toString();
String expected = s("<input name='name' type='submit' value='val1' tabindex='1' id='id1' class='class1' style='style1'></input>");
String expected = s("<input name='name' type='submit' value='val1' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1'></input>");
assertEquals(expected, output);
}
@@ -44,7 +44,7 @@ public class TextAreaTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<textarea name='name' cols='2' rows='1' tabindex='1' id='id1' class='class1' style='style1' title='title'>val1</textarea>");
String expected = s("<textarea name='name' cols='2' rows='1' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1' title='title'>val1</textarea>");
assertEquals(expected, output);
}
@@ -63,7 +63,7 @@ public class TextAreaTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<textarea name='' cols='' rows='' tabindex='1' id='id1' class='class1' style='style1' title='title'>val1</textarea>");
String expected = s("<textarea name='' cols='' rows='' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1' title='title'>val1</textarea>");
assertEquals(expected, output);
}
@@ -44,7 +44,7 @@ public class TextFieldTest extends AbstractCommonAttributesTest {
map.putAll(tag.getParameters());
theme.renderTag(getTagName(), context);
String output = writer.getBuffer().toString();
String expected = s("<input type='text' name='name' size='10' maxlength='11' value='val1' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
String expected = s("<input type='text' name='name' size='10' maxlength='11' value='val1' disabled='disabled' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
assertEquals(expected, output);
}
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-jfreechart-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-json-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-junit-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-osgi-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-oval-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-pell-multipart-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-plexus-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-plugins</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-portlet-mocks-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-portlet-tiles-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-portlet-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-rest-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-sitemesh-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-spring-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-testng-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-tiles-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-velocity-plugin</artifactId>
+6 -12
View File
@@ -29,7 +29,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>struts2-parent</artifactId>
<version>6.0.0-RC2</version>
<version>6.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Struts 2</name>
<url>http://struts.apache.org/</url>
@@ -51,7 +51,7 @@
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
<tag>STRUTS_6_0_0_RC2</tag>
<tag>HEAD</tag>
</scm>
<issueManagement>
@@ -104,14 +104,14 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2022-01-29T16:32:23Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2022-02-24T06:11:22Z</project.build.outputTimestamp>
<java.version>1.8</java.version>
<!-- dependency versions in alphanumeric order -->
<asm.version>9.2</asm.version>
<jackson.version>2.10.5</jackson.version>
<jackson-databind.version>2.10.5.1</jackson-databind.version>
<log4j2.version>2.17.1</log4j2.version>
<log4j2.version>2.17.2</log4j2.version>
<ognl.version>3.3.2</ognl.version>
<slf4j.version>1.7.32</slf4j.version>
<spring.platformVersion>4.3.30.RELEASE</spring.platformVersion>
@@ -933,12 +933,6 @@
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!-- it's a dependency of Velocity defined here to override the transitive one -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
@@ -1081,7 +1075,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
<version>4.3.1</version>
<exclusions>
<!-- The mockito-core artifact and easymock artifact use different versions of objenesis (2.6 vs 3.1).
Excluding the older version here to pass enforcer. When next upgrading mockito-core, confirm whether this exclusion is still required. -->
@@ -1134,7 +1128,7 @@
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.18</version>
<version>1.4.19</version>
<exclusions>
<exclusion>
<groupId>io.github.x-stream</groupId>