diff --git a/source/core-developers/constant-configuration.md b/source/core-developers/constant-configuration.md index e81c8e5262..06e20b2053 100644 --- a/source/core-developers/constant-configuration.md +++ b/source/core-developers/constant-configuration.md @@ -38,6 +38,9 @@ In the [default.properties](default-properties) file, each entry is treated as a In the [web.xml](web-xml) file, any FilterDispatcher initialization parameters are loaded as constants. +Since Struts 7.5.0 a package can override selected constants for its own actions, see +[Scoped constants](package-configuration#scoped-constants). + ### Value substitution Since Apache Struts 2.5.6 it is possible to use value substitution when defining `constant`s in `struts.xml` file. diff --git a/source/core-developers/package-configuration.md b/source/core-developers/package-configuration.md index 4c263704cd..f4d7da2bd1 100644 --- a/source/core-developers/package-configuration.md +++ b/source/core-developers/package-configuration.md @@ -80,3 +80,117 @@ The optional `abstract` attribute creates a base package that can omit the actio ``` + +## Scoped constants + +Since Struts 7.5.0 a package can override selected [constants](constant-configuration) for its own actions with +the `` element. Packages that `extends` it inherit the override, and an override declared in +the child package wins. When a package extends several packages (`extends="a, b"`) and they override the same +constant, the first listed package wins, the same rule as for result types. + +Packages created by the [Convention plugin](../plugins/convention/) have no `` of their own. +They only get overrides from their parent package, set with `@ParentPackage` or +`struts.convention.default.parent.package`. + +The element requires the [struts-7.5.dtd](../dtds/struts-7.5.dtd) and must come before any other child of +``: + +```xml + + + + + ... + + +``` + +| Attribute | Required | Description | +|-----------|----------|-------------------------------------------| +| name | **yes** | the name of a scopable constant | +| value | **yes** | the value used for actions of the package | + +Scoped constants are not a way to override an arbitrary constant per package. Constants are injected into +framework components once, at startup, so a package value only takes effect where a component explicitly reads +it per request. Such a component declares the constants it reads as _scopable_, and only those can appear +in ``. + +{:.alert .alert-warning} +Struts core does not declare any constant as scopable yet. A `` only works with a constant +declared scopable by a plugin or by your own application code. + +The configuration is validated at startup, and loading fails when: + +- a package declares a constant that nothing has declared scopable. This also applies to abstract packages. + The error message lists the scopable constants that are available. +- a package declares the same constant more than once. + +Values are used as they are. Unlike in ``, `${...}` [value substitution](constant-configuration#value-substitution) +is not supported. + +### Making a constant scopable + +A component makes a constant scopable in two steps. First, register a bean that implements +`org.apache.struts2.config.ScopableConstants` and returns the constant names. You can register more than one +such bean. The scopable set is the union of all of them. + +```java +import org.apache.struts2.config.ScopableConstants; + +import java.util.Set; + +public class MyScopableConstants implements ScopableConstants { + @Override + public Set getNames() { + return Set.of("myplugin.theme"); + } +} +``` + +```xml + +``` + +Second, read the value per request through `org.apache.struts2.config.ScopedConstantProvider`, not through +an `@Inject`-ed constant: + +```java +import org.apache.struts2.config.ScopedConstantProvider; +import org.apache.struts2.inject.Inject; + +private ScopedConstantProvider scopedConstantProvider; + +@Inject +public void setScopedConstantProvider(ScopedConstantProvider scopedConstantProvider) { + this.scopedConstantProvider = scopedConstantProvider; +} + +public String getTheme() { + return scopedConstantProvider.getValue("myplugin.theme"); +} +``` + +{:.alert .alert-info} +Use `org.apache.struts2.inject.Inject`. The Struts container ignores `jakarta.inject.Inject` and +`javax.inject.Inject`, so the setter would never be called. + +`getValue(name)` returns the value for the package of the action being executed, taking `extends` into account. +Outside an action invocation, or when no package in the hierarchy overrides the constant, it returns the global +constant, or `null` when that is not set. It throws `IllegalArgumentException` for a name that is not scopable. +The global value injected elsewhere is never changed. + +Some constants can never be scoped, because they are read before the package of a request is known, for +example the [ActionMapper](action-mapper) or the multipart upload limits. To use a different mapper per URL space, +use the [PrefixBasedActionMapper](action-mapper#prefixbasedactionmapper) instead. + +{:.alert .alert-danger} +Do not declare security-related constants scopable, such as `struts.parameters.requireAnnotations`, +`struts.enable.DynamicMethodInvocation` or `struts.devMode`. Otherwise a single package can relax a setting +meant to protect the whole application. + +The default `ScopedConstantProvider` implementation can be replaced with the `struts.scopedConstantProvider` +[extension point](../plugins/plugins-architecture#extension-points). For now this works only from XML or +properties configuration. Java-based configuration cannot set it yet. diff --git a/source/dtds/struts-6.5.dtd b/source/dtds/struts-6.5.dtd new file mode 100644 index 0000000000..6c2ef0d05b --- /dev/null +++ b/source/dtds/struts-6.5.dtd @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/dtds/struts-7.5.dtd b/source/dtds/struts-7.5.dtd new file mode 100644 index 0000000000..307f5f0e0a --- /dev/null +++ b/source/dtds/struts-7.5.dtd @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/plugins/plugins-architecture.md b/source/plugins/plugins-architecture.md index 3d0f309059..0a0ae6bd4c 100644 --- a/source/plugins/plugins-architecture.md +++ b/source/plugins/plugins-architecture.md @@ -114,6 +114,7 @@ The following extension points are available in Struts 2: | struts.securityMemberAccess | Define a custom SecurityMemberAccess implementation, used to restrict OGNL evaluations based on classes involved (since 6.4.0) | prototype | com.opensymphony.xwork2.ognl.SecurityMemberAccess | | struts.compoundRootAccessor | Define a custom CompoundRootAccessor implementation, used to resolve classes and manipulate the CompoundRoot (since 6.4.0) | singleton | com.opensymphony.xwork2.ognl.accessor.RootAccessor | | struts.methodAccessor | Define a custom MethodAccessor implementation, used to evaluate OGNL method calls (since 6.4.0) | singleton | ognl.MethodAccessor | +| struts.scopedConstantProvider | Resolves package-scoped constants declared with `` (since 7.5.0) | singleton | org.apache.struts2.config.ScopedConstantProvider | ## Plugin Examples