Skip to content

Commit 61665bb

Browse files
authored
Merge branch 'spring-projects:main' into refactor-for-issue#14768
2 parents b226581 + 902aff4 commit 61665bb

File tree

58 files changed

+400
-466
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+400
-466
lines changed

.github/workflows/continuous-integration-workflow.yml

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,31 @@ jobs:
3939
toolchain: 17
4040
with:
4141
java-version: ${{ matrix.java-version }}
42-
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.1.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2023.1.+ --stacktrace
42+
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ --stacktrace
4343
secrets: inherit
44-
check-samples:
45-
name: Check Samples
46-
runs-on: ubuntu-latest
47-
if: ${{ github.repository_owner == 'spring-projects' }}
48-
steps:
49-
- uses: actions/checkout@v4
50-
- name: Set up gradle
51-
uses: spring-io/spring-gradle-build-action@v2
52-
with:
53-
java-version: 17
54-
distribution: temurin
55-
- name: Check samples project
56-
env:
57-
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
58-
SAMPLES_DIR: ../spring-security-samples
59-
run: |
60-
# Extract version from gradle.properties
61-
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
62-
# Extract samplesBranch from gradle.properties
63-
samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}')
64-
./gradlew publishMavenJavaPublicationToLocalRepository
65-
./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR"
66-
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests
44+
# check-samples:
45+
# name: Check Samples
46+
# runs-on: ubuntu-latest
47+
# if: ${{ github.repository_owner == 'spring-projects' }}
48+
# steps:
49+
# - uses: actions/checkout@v4
50+
# - name: Set up gradle
51+
# uses: spring-io/spring-gradle-build-action@v2
52+
# with:
53+
# java-version: 17
54+
# distribution: temurin
55+
# - name: Check samples project
56+
# env:
57+
# LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
58+
# SAMPLES_DIR: ../spring-security-samples
59+
# run: |
60+
# # Extract version from gradle.properties
61+
# version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
62+
# # Extract samplesBranch from gradle.properties
63+
# samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}')
64+
# ./gradlew publishMavenJavaPublicationToLocalRepository
65+
# ./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR"
66+
# ./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests
6767
check-tangles:
6868
name: Check for Package Tangles
6969
runs-on: ubuntu-latest
@@ -82,21 +82,21 @@ jobs:
8282
./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
8383
deploy-artifacts:
8484
name: Deploy Artifacts
85-
needs: [ build, test, check-samples, check-tangles ]
85+
needs: [ build, test, check-tangles ]
8686
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
8787
with:
8888
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
8989
secrets: inherit
9090
deploy-docs:
9191
name: Deploy Docs
92-
needs: [ build, test, check-samples, check-tangles ]
92+
needs: [ build, test, check-tangles ]
9393
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
9494
with:
9595
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
9696
secrets: inherit
9797
deploy-schema:
9898
name: Deploy Schema
99-
needs: [ build, test, check-samples, check-tangles ]
99+
needs: [ build, test, check-tangles ]
100100
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1
101101
with:
102102
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Update Antora UI Spring
2+
3+
on:
4+
schedule:
5+
- cron: '0 10 * * *' # Once per day at 10am UTC
6+
workflow_dispatch:
7+
8+
permissions:
9+
pull-requests: write
10+
issues: write
11+
contents: write
12+
13+
jobs:
14+
update-antora-ui-spring:
15+
runs-on: ubuntu-latest
16+
name: Update on Supported Branches
17+
strategy:
18+
matrix:
19+
branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ]
20+
steps:
21+
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@852920ba3fb1f28b35a2f13201133bc00ef33677
22+
name: Update
23+
with:
24+
docs-branch: ${{ matrix.branch }}
25+
token: ${{ secrets.GITHUB_TOKEN }}
26+
antora-file-path: 'docs/antora-playbook.yml'
27+
update-antora-ui-spring-docs-build:
28+
runs-on: ubuntu-latest
29+
name: Update on docs-build
30+
steps:
31+
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@852920ba3fb1f28b35a2f13201133bc00ef33677
32+
name: Update
33+
with:
34+
docs-branch: 'docs-build'
35+
token: ${{ secrets.GITHUB_TOKEN }}

config/spring-security-config.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ dependencies {
6565
testImplementation 'jakarta.websocket:jakarta.websocket-api'
6666
testImplementation 'jakarta.websocket:jakarta.websocket-client-api'
6767
testImplementation 'ldapsdk:ldapsdk:4.1'
68-
testImplementation('net.sourceforge.htmlunit:htmlunit') {
68+
testImplementation('org.htmlunit:htmlunit') {
6969
exclude group: 'commons-logging', module: 'commons-logging'
7070
exclude group: 'xml-apis', module: 'xml-apis'
7171
}
@@ -80,7 +80,7 @@ dependencies {
8080
testImplementation "org.hibernate.orm:hibernate-core"
8181
testImplementation 'org.hsqldb:hsqldb'
8282
testImplementation 'org.mockito:mockito-core'
83-
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
83+
testImplementation('org.seleniumhq.selenium:htmlunit3-driver') {
8484
exclude group: 'commons-logging', module: 'commons-logging'
8585
exclude group: 'xml-apis', module: 'xml-apis'
8686
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFirewallTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.security.config.annotation.web.configurers;
1818

1919
import jakarta.servlet.http.HttpServletRequest;
20+
import org.junit.jupiter.api.Disabled;
2021
import org.junit.jupiter.api.Test;
2122
import org.junit.jupiter.api.extension.ExtendWith;
2223

@@ -52,6 +53,7 @@ public class NamespaceHttpFirewallTests {
5253
MockMvc mvc;
5354

5455
@Test
56+
@Disabled("MockMvc uses UriComponentsBuilder::fromUriString which was changed in https://github.com/spring-projects/spring-framework/issues/32513")
5557
public void requestWhenPathContainsDoubleDotsThenBehaviorMatchesNamespace() throws Exception {
5658
this.rule.register(HttpFirewallConfig.class).autowire();
5759
this.mvc.perform(get("/public/../private/")).andExpect(status().isBadRequest());

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import java.util.Map;
2626
import java.util.concurrent.ConcurrentHashMap;
2727

28-
import com.gargoylesoftware.htmlunit.util.UrlUtils;
2928
import com.nimbusds.jose.jwk.JWKSet;
3029
import com.nimbusds.jose.jwk.RSAKey;
3130
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
@@ -41,6 +40,7 @@
4140
import okhttp3.mockwebserver.MockResponse;
4241
import okhttp3.mockwebserver.MockWebServer;
4342
import okhttp3.mockwebserver.RecordedRequest;
43+
import org.htmlunit.util.UrlUtils;
4444
import org.junit.jupiter.api.Test;
4545
import org.junit.jupiter.api.extension.ExtendWith;
4646

config/src/test/java/org/springframework/security/config/http/AccessDeniedConfigTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818

1919
import jakarta.servlet.http.HttpServletRequest;
2020
import jakarta.servlet.http.HttpServletResponse;
21-
import org.eclipse.jetty.http.HttpStatus;
2221
import org.junit.jupiter.api.Test;
2322
import org.junit.jupiter.api.extension.ExtendWith;
2423

2524
import org.springframework.beans.factory.BeanCreationException;
2625
import org.springframework.beans.factory.annotation.Autowired;
2726
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
27+
import org.springframework.http.HttpStatus;
2828
import org.springframework.security.access.AccessDeniedException;
2929
import org.springframework.security.config.test.SpringTestContext;
3030
import org.springframework.security.config.test.SpringTestContextExtension;
@@ -71,7 +71,7 @@ public void configureWhenAccessDeniedHandlerIsMissingLeadingSlashThenException()
7171
@WithMockUser
7272
public void configureWhenAccessDeniedHandlerRefThenAutowire() throws Exception {
7373
this.spring.configLocations(this.xml("AccessDeniedHandler")).autowire();
74-
this.mvc.perform(get("/")).andExpect(status().is(HttpStatus.GONE_410));
74+
this.mvc.perform(get("/")).andExpect(status().is(HttpStatus.GONE.value()));
7575
}
7676

7777
@Test
@@ -90,7 +90,7 @@ public static class GoneAccessDeniedHandler implements AccessDeniedHandler {
9090
@Override
9191
public void handle(HttpServletRequest request, HttpServletResponse response,
9292
AccessDeniedException accessDeniedException) {
93-
response.setStatus(HttpStatus.GONE_410);
93+
response.setStatus(HttpStatus.GONE.value());
9494
}
9595

9696
}

config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
import jakarta.servlet.Filter;
2323
import jakarta.servlet.http.HttpServletRequest;
2424
import jakarta.servlet.http.HttpServletResponse;
25-
import org.eclipse.jetty.http.HttpStatus;
2625
import org.junit.jupiter.api.Test;
2726
import org.junit.jupiter.api.extension.ExtendWith;
2827

2928
import org.springframework.beans.factory.annotation.Autowired;
3029
import org.springframework.http.HttpMethod;
30+
import org.springframework.http.HttpStatus;
3131
import org.springframework.mock.web.MockHttpServletRequest;
3232
import org.springframework.mock.web.MockHttpSession;
3333
import org.springframework.security.access.AccessDeniedException;
@@ -566,7 +566,7 @@ private static class TeapotAccessDeniedHandler implements AccessDeniedHandler {
566566
@Override
567567
public void handle(HttpServletRequest request, HttpServletResponse response,
568568
AccessDeniedException accessDeniedException) {
569-
response.setStatus(HttpStatus.IM_A_TEAPOT_418);
569+
response.setStatus(HttpStatus.I_AM_A_TEAPOT.value());
570570
}
571571

572572
}

config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.util.List;
2727
import java.util.Map;
2828

29-
import com.gargoylesoftware.htmlunit.util.UrlUtils;
3029
import com.nimbusds.jose.jwk.JWKSet;
3130
import com.nimbusds.jose.jwk.RSAKey;
3231
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
@@ -40,6 +39,7 @@
4039
import okhttp3.mockwebserver.MockResponse;
4140
import okhttp3.mockwebserver.MockWebServer;
4241
import okhttp3.mockwebserver.RecordedRequest;
42+
import org.htmlunit.util.UrlUtils;
4343
import org.junit.jupiter.api.Test;
4444
import org.junit.jupiter.api.extension.ExtendWith;
4545
import reactor.core.publisher.Mono;

config/src/test/java/org/springframework/security/htmlunit/server/HtmlUnitWebTestClient.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
import java.util.Set;
2525
import java.util.StringTokenizer;
2626

27-
import com.gargoylesoftware.htmlunit.FormEncodingType;
28-
import com.gargoylesoftware.htmlunit.WebClient;
29-
import com.gargoylesoftware.htmlunit.WebRequest;
30-
import com.gargoylesoftware.htmlunit.util.NameValuePair;
27+
import org.htmlunit.FormEncodingType;
28+
import org.htmlunit.WebClient;
29+
import org.htmlunit.WebRequest;
30+
import org.htmlunit.util.Cookie;
31+
import org.htmlunit.util.NameValuePair;
3132
import reactor.core.publisher.Mono;
3233

3334
import org.springframework.http.HttpMethod;
@@ -117,8 +118,8 @@ private void cookies(WebTestClient.RequestBodySpec request, WebRequest webReques
117118
request.cookie(cookieName, cookieValue);
118119
}
119120
}
120-
Set<com.gargoylesoftware.htmlunit.util.Cookie> managedCookies = this.webClient.getCookies(webRequest.getUrl());
121-
for (com.gargoylesoftware.htmlunit.util.Cookie cookie : managedCookies) {
121+
Set<Cookie> managedCookies = this.webClient.getCookies(webRequest.getUrl());
122+
for (Cookie cookie : managedCookies) {
122123
request.cookie(cookie.getName(), cookie.getValue());
123124
}
124125
}

config/src/test/java/org/springframework/security/htmlunit/server/MockWebResponseBuilder.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
import java.util.ArrayList;
2121
import java.util.List;
2222

23-
import com.gargoylesoftware.htmlunit.WebRequest;
24-
import com.gargoylesoftware.htmlunit.WebResponse;
25-
import com.gargoylesoftware.htmlunit.WebResponseData;
26-
import com.gargoylesoftware.htmlunit.util.NameValuePair;
23+
import org.htmlunit.WebRequest;
24+
import org.htmlunit.WebResponse;
25+
import org.htmlunit.WebResponseData;
26+
import org.htmlunit.util.NameValuePair;
2727

2828
import org.springframework.http.HttpHeaders;
2929
import org.springframework.http.HttpStatus;

config/src/test/java/org/springframework/security/htmlunit/server/WebTestClientHtmlUnitDriverBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
package org.springframework.security.htmlunit.server;
1818

19-
import com.gargoylesoftware.htmlunit.WebClient;
20-
import com.gargoylesoftware.htmlunit.WebConnection;
19+
import org.htmlunit.WebClient;
20+
import org.htmlunit.WebConnection;
2121
import org.openqa.selenium.WebDriver;
2222

2323
import org.springframework.test.web.reactive.server.WebTestClient;

config/src/test/java/org/springframework/security/htmlunit/server/WebTestClientWebConnection.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
import java.io.IOException;
2020

21-
import com.gargoylesoftware.htmlunit.WebClient;
22-
import com.gargoylesoftware.htmlunit.WebConnection;
23-
import com.gargoylesoftware.htmlunit.WebRequest;
24-
import com.gargoylesoftware.htmlunit.WebResponse;
21+
import org.htmlunit.WebClient;
22+
import org.htmlunit.WebConnection;
23+
import org.htmlunit.WebRequest;
24+
import org.htmlunit.WebResponse;
2525

2626
import org.springframework.lang.Nullable;
2727
import org.springframework.test.web.reactive.server.FluxExchangeResult;

dependencies/spring-security-dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ dependencies {
4747
api libs.jakarta.websocket.jakarta.websocket.client.api
4848
api libs.ldapsdk
4949
api libs.net.sourceforge.htmlunit
50+
api libs.org.htmlunit.htmlunit
5051
api libs.org.apache.directory.server.apacheds.entry
5152
api libs.org.apache.directory.server.apacheds.core
5253
api libs.org.apache.directory.server.apacheds.protocol.ldap

docs/antora-playbook.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ urls:
3030
redirect_facility: httpd
3131
ui:
3232
bundle:
33-
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip
33+
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip
3434
snapshot: true
3535
runtime:
3636
log:

docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ A given invocation to `MyCustomerService#readCustomer` may look something like t
117117

118118
image::{figures}/methodsecurity.png[]
119119

120-
1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor/html[`AuthorizationManagerBeforeMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
120+
1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor`] that matches <<annotation-method-pointcuts,the `@PreAuthorize` pointcut>>
121121
2. The interceptor invokes {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager#check`]
122122
3. The authorization manager uses a `MethodSecurityExpressionHandler` to parse the annotation's <<authorization-expressions,SpEL expression>> and constructs a corresponding `EvaluationContext` from a `MethodSecurityExpressionRoot` containing xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[a `Supplier<Authentication>`] and `MethodInvocation`.
123123
4. The interceptor uses this context to evaluate the expression; specifically, it reads xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] from the `Supplier` and checks whether it has `permission:read` in its collection of xref:servlet/authorization/architecture.adoc#authz-authorities[authorities]

gradle/libs.versions.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ org-jetbrains-kotlin = "1.9.24"
1313
org-jetbrains-kotlinx = "1.8.1"
1414
org-mockito = "5.11.0"
1515
org-opensaml = "4.3.2"
16-
org-springframework = "6.1.9"
16+
org-springframework = "6.2.0-M4"
1717

1818
[libraries]
1919
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.6"
@@ -46,6 +46,7 @@ jakarta-websocket-jakarta-websocket-client-api = { module = "jakarta.websocket:j
4646
jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.2"
4747
ldapsdk = "ldapsdk:ldapsdk:4.1"
4848
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
49+
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.1.0"
4950
org-apache-directory-server-apacheds-core = { module = "org.apache.directory.server:apacheds-core", version.ref = "org-apache-directory-server" }
5051
org-apache-directory-server-apacheds-entry = { module = "org.apache.directory.server:apacheds-core-entry", version.ref = "org-apache-directory-server" }
5152
org-apache-directory-server-apacheds-protocol-ldap = { module = "org.apache.directory.server:apacheds-protocol-ldap", version.ref = "org-apache-directory-server" }
@@ -78,8 +79,8 @@ org-opensaml-opensaml-core = { module = "org.opensaml:opensaml-core", version.re
7879
org-opensaml-opensaml-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml" }
7980
org-opensaml-opensaml-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml" }
8081
org-python-jython = { module = "org.python:jython", version = "2.5.3" }
81-
org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit-driver:2.70.0"
82-
org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3.141.59"
82+
org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.20.0"
83+
org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.20.0"
8384
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
8485
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1"
8586
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"

0 commit comments

Comments
 (0)