Repository navigation
WW-3476 docs: document package-scoped constants #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,3 +80,117 @@ The optional `abstract` attribute creates a base package that can omit the actio | |
| </package> | ||
| </struts> | ||
| ``` | ||
|
|
||
| ## Scoped constants | ||
|
|
||
| Since Struts 7.5.0 a package can override selected [constants](constant-configuration) for its own actions with | ||
| the `<scoped-constant>` 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 `<scoped-constant>` 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 | ||
| `<package>`: | ||
|
|
||
| ```xml | ||
| <!DOCTYPE struts PUBLIC | ||
| "-//Apache Software Foundation//DTD Struts Configuration 7.5//EN" | ||
| "https://struts.apache.org/dtds/struts-7.5.dtd"> | ||
| <struts> | ||
| <package name="admin" extends="struts-default" namespace="/admin"> | ||
| <scoped-constant name="some.scopable.constant" value="x"/> | ||
| ... | ||
| </package> | ||
| </struts> | ||
| ``` | ||
|
|
||
| | 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 `<scoped-constant>`. | ||
|
|
||
| {:.alert .alert-warning} | ||
| Struts core does not declare any constant as scopable yet. A `<scoped-constant>` 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 `<constant>`, `${...}` [value substitution](constant-configuration#value-substitution) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving this as is for now. If WW-5767 lands before 7.5.0, the sentence will be replaced with a link to 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<String> getNames() { | ||
| return Set.of("myplugin.theme"); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ```xml | ||
| <bean type="org.apache.struts2.config.ScopableConstants" name="myplugin" | ||
| class="com.example.MyScopableConstants"/> | ||
| ``` | ||
|
|
||
| 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 | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest showing the imports here, at least
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 1d215b5. Both snippets now show their imports, including |
||
| 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` | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| [extension point](../plugins/plugins-architecture#extension-points). For now this works only from XML or | ||
| properties configuration. Java-based configuration cannot set it yet. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!-- | ||
| /* | ||
| * 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. | ||
| */ | ||
| --> | ||
| <!-- START SNIPPET: strutsDtd --> | ||
|
|
||
| <!-- | ||
| Struts configuration DTD. | ||
| Use the following DOCTYPE | ||
|
|
||
| <!DOCTYPE struts PUBLIC | ||
| "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN" | ||
| "https://struts.apache.org/dtds/struts-6.5.dtd"> | ||
| --> | ||
|
|
||
| <!ELEMENT struts ((package|include|bean|constant)*,bean-selection?, unknown-handler-stack?)> | ||
| <!ATTLIST struts | ||
| order CDATA #IMPLIED | ||
| > | ||
|
|
||
| <!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-allowed-methods?, global-exception-mappings?, action*)> | ||
| <!ATTLIST package | ||
| name CDATA #REQUIRED | ||
| extends CDATA #IMPLIED | ||
| namespace CDATA #IMPLIED | ||
| abstract CDATA #IMPLIED | ||
| final CDATA #IMPLIED | ||
| strict-method-invocation (true|false) "true" | ||
| > | ||
|
|
||
| <!ELEMENT result-types (result-type+)> | ||
|
|
||
| <!ELEMENT result-type (param*)> | ||
| <!ATTLIST result-type | ||
| name CDATA #REQUIRED | ||
| class CDATA #REQUIRED | ||
| default (true|false) "false" | ||
| > | ||
|
|
||
| <!ELEMENT interceptors (interceptor|interceptor-stack)+> | ||
|
|
||
| <!ELEMENT interceptor (param*)> | ||
| <!ATTLIST interceptor | ||
| name CDATA #REQUIRED | ||
| class CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT interceptor-stack (interceptor-ref*)> | ||
| <!ATTLIST interceptor-stack | ||
| name CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT interceptor-ref (param*)> | ||
| <!ATTLIST interceptor-ref | ||
| name CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT default-interceptor-ref (#PCDATA)> | ||
| <!ATTLIST default-interceptor-ref | ||
| name CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT default-action-ref (#PCDATA)> | ||
| <!ATTLIST default-action-ref | ||
| name CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT default-class-ref (#PCDATA)> | ||
| <!ATTLIST default-class-ref | ||
| class CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT global-results (result+)> | ||
|
|
||
| <!ELEMENT global-allowed-methods (#PCDATA)> | ||
|
|
||
| <!ELEMENT global-exception-mappings (exception-mapping+)> | ||
|
|
||
| <!ELEMENT action ((param|result|interceptor-ref|exception-mapping)*,allowed-methods?)> | ||
| <!ATTLIST action | ||
| name CDATA #REQUIRED | ||
| class CDATA #IMPLIED | ||
| method CDATA #IMPLIED | ||
| converter CDATA #IMPLIED | ||
| > | ||
|
|
||
| <!ELEMENT param (#PCDATA)> | ||
| <!ATTLIST param | ||
| name CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT result (#PCDATA|param)*> | ||
| <!ATTLIST result | ||
| name CDATA #IMPLIED | ||
| type CDATA #IMPLIED | ||
| > | ||
|
|
||
| <!ELEMENT exception-mapping (#PCDATA|param)*> | ||
| <!ATTLIST exception-mapping | ||
| name CDATA #IMPLIED | ||
| exception CDATA #REQUIRED | ||
| result CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT allowed-methods (#PCDATA)> | ||
|
|
||
| <!ELEMENT include (#PCDATA)> | ||
| <!ATTLIST include | ||
| file CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT bean (#PCDATA)> | ||
| <!ATTLIST bean | ||
| type CDATA #IMPLIED | ||
| name CDATA #IMPLIED | ||
| class CDATA #REQUIRED | ||
| scope CDATA #IMPLIED | ||
| static CDATA #IMPLIED | ||
| optional CDATA #IMPLIED | ||
| > | ||
|
|
||
| <!ELEMENT bean-selection (#PCDATA)> | ||
| <!ATTLIST bean-selection | ||
| name CDATA #IMPLIED | ||
| class CDATA #IMPLIED | ||
| > | ||
|
|
||
| <!ELEMENT constant (#PCDATA)> | ||
| <!ATTLIST constant | ||
| name CDATA #REQUIRED | ||
| value CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!ELEMENT unknown-handler-stack (unknown-handler-ref*)> | ||
| <!ELEMENT unknown-handler-ref (#PCDATA)> | ||
| <!ATTLIST unknown-handler-ref | ||
| name CDATA #REQUIRED | ||
| > | ||
|
|
||
| <!-- END SNIPPET: strutsDtd --> | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two small additions could go here:
extends="a,b"), the first listed parent wins on a conflict, the same rule as result types.@ParentPackage).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 1d215b5. Both points are added. I checked the first-parent rule against the code:
PackageConfig.Builder.addParentinserts at index 0, so the first package inextendsis merged last and wins. For Convention I also mentionedstruts.convention.default.parent.package, since it is the fallback when@ParentPackageis missing.